【设计模式——学习笔记】23种设计模式——装饰器模式Decorator(原理讲解+应用场景介绍+案例介绍+Java代码实现)

文章目录

  • 生活案例
    • 咖啡厅 咖啡定制案例
  • 装饰者模式介绍
    • 介绍
    • 出场角色
  • 案例实现
    • 案例一(咖啡厅问题)
      • 类图
      • 代码实现
      • 咖啡样式拓展代码实现
    • 案例二
      • 类图
      • 代码实现
  • 装饰着模式在IO流源码的应用
  • 总结
    • 什么是父类和子类的一致性
    • 如何让自己和被委托对象有一致性
  • 文章说明

生活案例

咖啡厅 咖啡定制案例

在咖啡厅中,有多种不同类型的咖啡,客户在预定了咖啡之后,还可以选择添加不同的调料来调整咖啡的口味,当客户点了咖啡添加了不同的调料,咖啡的价格需要做出相应的改变。

要求:程序实现具有良好的拓展性、改动方便、维护方便

【方案一】
在这里插入图片描述

写一个抽象类Drink,然后将所有咖啡和调料组合形成多个类来继承抽象类,缺点:当增加一个单品咖啡,或者调味,类的数量就会大增,产生类爆炸问题

【方案二】

在这里插入图片描述

分析:

  • 可以控制类的数量,不至于造成很多的类
  • 增加或者删除调料种类时,代码的维护量很大
  • 如果同样一种调料可以点多份时,可以将 hasMilk 返回一个对应int类型的数据来表示调料的份数

装饰者模式介绍

介绍

  • 动态的将新功能附加到对象上。在对象功能扩展方面,它比继承更好,装饰者模式也体现了开闭原则(ocp)
  • 假如有一块蛋糕,如果加上奶油,就变成了奶油蛋糕;再加上草莓,就变成了草莓奶油蛋糕。整个过程就是在不断装饰蛋糕的过程。根据装饰者模式编写的程序的对象与蛋糕十分相似。首先有一个相当于蛋糕的对象,然后像不断地装饰蛋糕一样地不断地对其增加功能,它就变成了使用目的更加明确的对象。

出场角色

  • Component(主体,被装饰对象):增加功能时的核心角色,定义了接口(API)
  • ConcreteComponent(具体主体):实现了Component角色所定义的接口
  • Decorator(装饰者):该角色具有与Component角色相同的接口(API),在它内部保存了Component角色
  • ConcreteDecorator( 具体的装饰者)

在这里插入图片描述

案例实现

案例一(咖啡厅问题)

类图

在这里插入图片描述

在这里插入图片描述

代码实现

【被装饰主体】

package com.atguigu.decorator;public abstract class Drink {/*** 描述*/public String des;/*** 价格*/private float price = 0.0f;public String getDes() {return des;}public void setDes(String des) {this.des = des;}public float getPrice() {return price;}public void setPrice(float price) {this.price = price;}/*** 计算费用的抽象方法,需要子类来实现* @return*/public abstract float cost();}

【缓冲类:整合所有咖啡的共同点,这个类不一定要写,要结合实际情况】

package com.atguigu.decorator;public class Coffee  extends Drink {@Overridepublic float cost() {return super.getPrice();}}

【单品咖啡:意大利咖啡】

package com.atguigu.decorator;public class Espresso extends Coffee {public Espresso() {setDes(" 意大利咖啡 ");// 初始化意大利咖啡的价格setPrice(6.0f);}
}

【单品咖啡:美式咖啡】

package com.atguigu.decorator;public class LongBlack extends Coffee {public LongBlack() {setDes(" longblack ");setPrice(5.0f);}
}

【单品咖啡:浓咖啡】

package com.atguigu.decorator;public class ShortBlack extends Coffee{public ShortBlack() {setDes(" shortblack ");setPrice(4.0f);}
}

【装饰者】

package com.atguigu.decorator;/*** 装饰物,继承了Drink,还聚合了Drink*/
public class Decorator extends Drink {private Drink obj;/*** 聚合Drink* @param obj*/public Decorator(Drink obj) {this.obj = obj;}@Overridepublic float cost() {// getPrice 自己价格 + 咖啡的价格return super.getPrice() + obj.cost();}/*** 输出信息* @return*/@Overridepublic String getDes() {// obj.getDes() 输出被装饰者的信息return des + " " + getPrice() + " && " + obj.getDes();}}

【具体装饰者:巧克力】

package com.atguigu.decorator;/*** 具体的Decorator, 这里就是调味品*/
public class Chocolate extends Decorator {public Chocolate(Drink obj) {super(obj);setDes(" 巧克力 ");// 调味品 的价格setPrice(3.0f); }}

【具体装饰者:牛奶】

package com.atguigu.decorator;public class Milk extends Decorator {public Milk(Drink obj) {super(obj);setDes(" 牛奶 ");setPrice(2.0f);}}

【具体装饰者:豆浆】

package com.atguigu.decorator;public class Soy extends Decorator{public Soy(Drink obj) {super(obj);setDes(" 豆浆  ");setPrice(1.5f);}}

【主类】

package com.atguigu.decorator;public class CoffeeBar {public static void main(String[] args) {System.out.println("============== 订单1 =============");// 装饰者模式下的订单:2份巧克力+一份牛奶的LongBlack// 1. 点一份 LongBlackDrink order = new LongBlack();System.out.println("费用=" + order.cost());System.out.println("描述=" + order.getDes());System.out.println();// 2.加入一份牛奶order = new Milk(order);System.out.println("order 加入一份牛奶 费用 =" + order.cost());System.out.println("order 加入一份牛奶 描述 = " + order.getDes());System.out.println();// 3.加入一份巧克力order = new Chocolate(order);System.out.println("order 加入一份牛奶 加入一份巧克力 费用 =" + order.cost());System.out.println("order 加入一份牛奶 加入一份巧克力 描述 = " + order.getDes());System.out.println();// 4.加入一份巧克力order = new Chocolate(order);System.out.println("order 加入一份牛奶 加入2份巧克力 费用 =" + order.cost());System.out.println("order 加入一份牛奶 加入2份巧克力 描述 = " + order.getDes());System.out.println();}}

【运行】

============== 订单1 =============
费用=5.0
描述= longblack order 加入一份牛奶 费用 =7.0
order 加入一份牛奶 描述 =  牛奶  2.0 &&  longblack order 加入一份牛奶 加入一份巧克力 费用 =10.0
order 加入一份牛奶 加入一份巧克力 描述 =  巧克力  3.0 &&  牛奶  2.0 &&  longblack order 加入一份牛奶 加入2份巧克力 费用 =13.0
order 加入一份牛奶 加入2份巧克力 描述 =  巧克力  3.0 &&  巧克力  3.0 &&  牛奶  2.0 &&  longblack 

咖啡样式拓展代码实现

只需要新增一个单品咖啡类,就可以购买了,拓展性非常强大

【新增单品咖啡:无因咖啡】

package com.atguigu.decorator;public class DeCaf extends Coffee {public DeCaf() {setDes(" 无因咖啡 ");setPrice(1.0f);}
}

【主类】

package com.atguigu.decorator;public class CoffeeBar {public static void main(String[] args) {System.out.println("============== 订单2 =============");Drink order2 = new DeCaf();System.out.println("order2 无因咖啡 费用 =" + order2.cost());System.out.println("order2 无因咖啡 描述 = " + order2.getDes());System.out.println();order2 = new Milk(order2);System.out.println("order2 无因咖啡 加入一份牛奶 费用 =" + order2.cost());System.out.println("order2 无因咖啡 加入一份牛奶 描述 = " + order2.getDes());System.out.println();}}

【运行】

============== 订单2 =============
order2 无因咖啡 费用 =1.0
order2 无因咖啡 描述 =  无因咖啡 order2 无因咖啡 加入一份牛奶 费用 =3.0
order2 无因咖啡 加入一份牛奶 描述 =  牛奶  2.0 &&  无因咖啡 Process finished with exit code 0

案例二

类图

在这里插入图片描述

代码实现

【抽象主体】

package com.atguigu.decorator.Sample;public abstract class Display {/*** 获取横向字符数(抽象方法,需要子类去实现)* @return*/public abstract int getColumns();/*** 获取纵向行数(抽象方法,需要子类去实现)* @return*/public abstract int getRows();/*** 获取第row行的字符串(抽象方法,需要子类去实现)* @param row* @return*/public abstract String getRowText(int row);/*** 显示所有行的字符串*/public void show() {// 遍历行数for (int i = 0; i < getRows(); i++) {// 获取改行的字符串来打印出来System.out.println(getRowText(i));}}
}

【具体主体】

package com.atguigu.decorator.Sample;/*** 该类用来显示单行字符串*/
public class StringDisplay extends Display {/*** 要显示的字符串*/private String string;/*** 构造方法** @param string 要显示的字符串*/public StringDisplay(String string) {this.string = string;}@Overridepublic int getColumns() {// 字符数return string.getBytes().length;}@Overridepublic int getRows() {// 行数是1return 1;}/*** 只有第0行可以获取到字符串,其他都是空* @param row* @return*/@Overridepublic String getRowText(int row) {// 仅当row为0时返回值if (row == 0) {return string;} else {return null;}}
}

【抽象装饰者】

package com.atguigu.decorator.Sample;/*** 装饰者抽象类,注意要继承抽象主体,并聚合抽象主体*/
public abstract class Border extends Display {/*** 表示被装饰物*/protected Display display;protected Border(Display display) {// 在生成实例时通过参数指定被装饰物this.display = display;}
}

【具体修饰者1】

package com.atguigu.decorator.Sample;/*** 在字符串的左右两侧添加边框*/
public class SideBorder extends Border {/*** 表示装饰边框的字符*/private char borderChar;/*** 通过构造函数指定Display和装饰边框字符* @param display* @param ch*/public SideBorder(Display display, char ch) {super(display);this.borderChar = ch;}/*** 字符数为字符串字符数加上两侧边框字符数* @return*/public int getColumns() {return 1 + display.getColumns() + 1;}/*** 行数即被装饰物的行数* @return*/public int getRows() {// 在字符串的两侧添加字符并不会增加行数,所以直接返回主体的行数即可return display.getRows();}/*** 指定的那一行的字符串为被装饰物的字符串加上两侧的边框的字符* @param row* @return*/public String getRowText(int row) {return borderChar + display.getRowText(row) + borderChar;}
}

【具体装饰者2】

package com.atguigu.decorator.Sample;/*** 在字符串的上下左右都加上装饰框*/
public class FullBorder extends Border {public FullBorder(Display display) {super(display);}public int getColumns() {// 字符数为被装饰物的字符数加上两侧边框字符数return 1 + display.getColumns() + 1;}public int getRows() {// 行数为被装饰物的行数加上上下边框的行数return 1 + display.getRows() + 1;}/*** 指定的那一行的字符串** @param row* @return*/public String getRowText(int row) {if (row == 0) {                                                 // 上边框return "+" + makeLine('-', display.getColumns()) + "+";} else if (row == display.getRows() + 1) {                      // 下边框return "+" + makeLine('-', display.getColumns()) + "+";} else {                                                        // 其他边框return "|" + display.getRowText(row - 1) + "|";}}/*** 生成一个重复count次字符ch的字符串** @param ch* @param count* @return*/private String makeLine(char ch, int count) {StringBuffer buf = new StringBuffer();for (int i = 0; i < count; i++) {buf.append(ch);}return buf.toString();}
}

【主类】

package com.atguigu.decorator.Sample;public class Main {public static void main(String[] args) {Display b1 = new StringDisplay("Hello, world.");Display b2 = new SideBorder(b1, '#');Display b3 = new FullBorder(b2);b1.show();b2.show();b3.show();Display b4 =new SideBorder(new FullBorder(new FullBorder(new SideBorder(new FullBorder(new StringDisplay("你好,世界。")),'*'))),'/');b4.show();}
}

【运行】

Hello, world.#Hello, world.#+---------------+
|#Hello, world.#|
+---------------+/+------------------------+/
/|+----------------------+|/
/||*+------------------+*||/
/||*|你好,世界。|*||/
/||*+------------------+*||/
/|+----------------------+|/
/+------------------------+/Process finished with exit code 0

装饰着模式在IO流源码的应用

在这里插入图片描述

  • InputStream 是抽象类, 类似我们前面讲的 Drink
  • FileInputStream 是 InputStream 子类,类似我们前面的 DeCaf, LongBlack
  • FilterInputStream 是 InputStream 子类:类似我们前面 的 Decorator 修饰者
  • DataInputStream 是 FilterInputStream 子类,具体的修饰者,类似前面的 Milk, Soy 等
  • FilterInputStream 类 有 protected volatile InputStream in; 即聚合了被装饰者

在这里插入图片描述

在这里插入图片描述

总结

  • 在装饰者模式中,装饰者与被装饰者具有一致性。装饰者类是表示被装饰者的类的子类,这就体现了它们之间的一致性,它们具有相同的接口,这样,就算被装饰者被装饰了,接口还是向外暴露的(接口的透明性)
  • 可以在不改变被装饰者的前提下增加功能,如案例中在显示字符串之前对字符串进行修饰
  • 只需要一些装饰物即可添加许多功能:通过自由组合调料,可以让咖啡拥有各种不同的味道
  • 装饰者模式也有缺点:会导致程序中增加许多功能类似的很小的类

什么是父类和子类的一致性

在这里插入图片描述

可以将子类的实例保存到父类的变量中,也可以直接调用从父类中继承的方法

如何让自己和被委托对象有一致性

使用委托让接口具有透明性时,自己和被委托对象具有一致性

在这里插入图片描述

Rose和Violet都有相同的method方法。Rose将method方法的处理委托给了 Violet。这两个类虽然都有 method 方法,但是没有明确在代码中体现出“共通性”。

如果要明确地表示method方法是共通的,只需要像下面这样编写一个抽象类Flower,然后让Rose和Violet都继承并实现方法即可。

在这里插入图片描述

或者让Flower作为接口

在这里插入图片描述

文章说明

  • 本文章为本人学习尚硅谷的学习笔记,文章中大部分内容来源于尚硅谷视频(点击学习尚硅谷相关课程),也有部分内容来自于自己的思考,发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识,如有侵权请联系删除,最后对尚硅谷的优质课程表示感谢。
  • 本人还同步阅读《图解设计模式》书籍(图解设计模式/(日)结城浩著;杨文轩译–北京:人民邮电出版社,2017.1),进而综合两者的内容,让知识点更加全面

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/16173.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深度学习和神经网络

人工神经网络分为两个阶段&#xff1a; 1 &#xff1a;接收来自其他n个神经元传递过来的信号&#xff0c;这些输入信号通过与相应的权重进行 加权求和传递给下个阶段。&#xff08;预激活阶段&#xff09; 2&#xff1a;把预激活的加权结果传递给激活函数 sum :加权 f:激活…

【Linux】UDP协议

​&#x1f320; 作者&#xff1a;阿亮joy. &#x1f386;专栏&#xff1a;《学会Linux》 &#x1f387; 座右铭&#xff1a;每个优秀的人都有一段沉默的时光&#xff0c;那段时光是付出了很多努力却得不到结果的日子&#xff0c;我们把它叫做扎根 目录 &#x1f449;传输层&a…

Mysql的锁

加锁的目的 对数据加锁是为了解决事务的隔离性问题&#xff0c;让事务之前相互不影响&#xff0c;每个事务进行操作的时候都必须先加上一把锁&#xff0c;防止其他事务同时操作数据。 事务的属性 &#xff08;ACID&#xff09; 原子性 一致性 隔离性 持久性 事务的隔离级别 锁…

Python入门【__init__ 构造方法和 __new__ 方法、类对象、类属性、类方法、静态方法、内存分析实例对象和类对象创建过程(重要)】(十四)

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱敲代码的小王&#xff0c;CSDN博客博主,Python小白 &#x1f4d5;系列专栏&#xff1a;python入门到实战、Python爬虫开发、Python办公自动化、Python数据分析、Python前后端开发 &#x1f4e7;如果文章知识点有错误…

[C++] 类与对象(上)

目录 1、前言 2、类的引入 3、类的定义 3.1 类的两种定义方式 4、类的访问限定符 5、类的作用域 6、类的实例化 7、类对象模型 7.1 内存对齐规则 7.1 类对象的存储方式 8、this指针 8.1 this指针的特性 8.2 this指针是否可以为空 1、前言 C语言是面向过程的&#…

DUBBO服务多网卡,服务调用失败

如果服务器是多网卡的&#xff0c;比如安装了docker&#xff0c;有一个docker虚拟网卡&#xff0c;一个实体网卡eth0&#xff0c;当我们运行springboot应用后&#xff0c;dubbo注入到zk的地址是 docker虚拟网卡的地址172网段&#xff0c;而不是实际内网地址192网段&#xff0c;…

类的封装和包(JAVA)

封装 所有的OOP语言都会有三个特征&#xff1a; 封装&#xff1b;继承&#xff1b;多态。 本篇文章会为大家带来有关封装的知识。 在我们日常生活中可以看到电视就只有那么几个按键&#xff08;开关&#xff0c;菜单……&#xff09;和一些接口&#xff0c;而而我们通过这些东…

【计算机视觉|人脸建模】SOFA:基于风格、由单一示例的2D关键点驱动的3D面部动画

本系列博文为深度学习/计算机视觉论文笔记&#xff0c;转载请注明出处 标题&#xff1a;SOFA: Style-based One-shot 3D Facial Animation Driven by 2D landmarks 链接&#xff1a;SOFA: Style-based One-shot 3D Facial Animation Driven by 2D landmarks | Proceedings of …

jmeter压力测试指标解释

目录 RT(response time) Throughput 吞吐量 并发用户数 QPS (query per seconds) TPS (transition per seconds) PV和UV 聚合报告&#xff1a; RT(response time) 什么是RT? RT就是指系统在接收到请求和做出相应这段时间跨度 但是值得一提的是RT的值越高,并不真的就能…

什么是云原生和 CNCF?

一、CNCF简介 CNCF&#xff1a;全称Cloud Native Computing Foundation&#xff08;云原生计算基金会&#xff09;&#xff0c;成立于 2015 年 12 月 11 日&#xff0c;是一个开源软件基金会&#xff0c;它致力于云原生&#xff08;Cloud Native&#xff09;技术的普及和可持续…

Klipper seria.c 文件代码分析

一. 前言 Klipper 底层硬件的串口模块程序写的是否正确是决定下位机与上位机能否正常通信的前提&#xff0c;如果这个文件的驱动没写好&#xff0c;那上位机控制下位机就无从谈起&#xff0c;更无法通过上位机去验证下位机程序的正确性。 本篇博文将详细解析 Klipper src 文件夹…

809协议

809协议 目录概述需求&#xff1a; 设计思路实现思路分析1.809协议数据流——链路管理类 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better result,wait for change,…

在idea中添加try/catch的快捷键

在idea中添加try/catch的快捷键 在idea中添加try/catch的快捷键 ctrlaltt 选中想被try/catch包围的语句&#xff0c;同时按下ctrlaltt&#xff0c; 出现下图 选择try/catch即可。

Elasticsearch搜索引擎系统入门

目录 【认识Elasticsearch】 Elasticsearch主要应用场景 Elasticsearch的版本与升级 【Elastic Stack全家桶】 Logstash Kibana Beats Elasticsearch在日志场景的应用 Elasticsearch与数据库的集成 【安装Elasticsearch】 安装插件 安装Kibana 安装Logstash 【认…

C# 2的幂

231 2的幂 给你一个整数 n&#xff0c;请你判断该整数是否是 2 的幂次方。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 如果存在一个整数 x 使得 n 2x &#xff0c;则认为 n 是 2 的幂次方。 示例 1&#xff1a; 输入&#xff1a;n 1 输出&a…

【图论】三种中心性 —— 特征向量、katz 和 PageRank

维基百科&#xff1a;在图论和网络分析中&#xff0c;中心性指标为图中相应网络位置的节点分配排名或数值。中心性这一概念最初起源于社交网络分析&#xff0c;因此很多衡量中心性的术语也反映了其社会学背景。 不同中心性指标对 “重要” 的衡量方式不同&#xff0c;因此适用于…

惊喜!1行Python代码,瞬间测你工作量,分享一个统计代码行数的神器

大家好&#xff0c;这里是程序员晚枫。 **你想不想知道一个项目中&#xff0c;自己写了多少行代码&#xff1f;**我用今天的工具统计了一下开源项目&#xff1a;python-office的代码行数&#xff0c;竟然有21w行&#xff01; 我们一起看一下怎么用最简单的方法&#xff0c;统…

mac下安装vue cli脚手架并搭建一个简易项目

目录 1、确定本电脑下node和npm版本是否为项目所需版本。 2、下载vue脚手架 3、创建项目 1、下载node。 如果有node&#xff0c;打开终端&#xff0c;输入node -v和npm -v , 确保node和npm的版本&#xff0c;(这里可以根据自己的需求去选择&#xff0c;如果对最新版本的内容有…

IO进程线程day3(2023.7.31)

一、Xmind整理&#xff1a; 文件描述符概念&#xff1a; 二、课上练习&#xff1a; 练习1&#xff1a;用fread和fwrite实现文件拷贝 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <head.h> int main(int argc, const char…

什么叫前后端分离?为什么需要前后端问题?解决了什么问题?

单体架构出现的问题 引出&#xff1a;来看一个单体项目架构的结构 通过上述可以看到单体架构主要存在以下几点问题&#xff1a; 开发人员同时负责前端和后端代码开发&#xff0c;分工不明确开发效率低前后端代码混合在一个工程中&#xff0c;不便于管理对开发人员要求高(既会前…