【设计模式——学习笔记】23种设计模式——装饰器模式Decorator(原理讲解+应用场景介绍+案例介绍+Java代码实现)
生活案例
咖啡厅 咖啡定制案例
在咖啡厅中,有多种不同类型的咖啡,客户在预定了咖啡之后,还可以选择添加不同的调料来调整咖啡的口味,当客户点了咖啡添加了不同的调料,咖啡的价格需要做出相应的改变。
要求:程序实现具有良好的拓展性、改动方便、维护方便
【方案一】

写一个抽象类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),进而综合两者的内容,让知识点更加全面
相关文章:
【设计模式——学习笔记】23种设计模式——装饰器模式Decorator(原理讲解+应用场景介绍+案例介绍+Java代码实现)
生活案例 咖啡厅 咖啡定制案例 在咖啡厅中,有多种不同类型的咖啡,客户在预定了咖啡之后,还可以选择添加不同的调料来调整咖啡的口味,当客户点了咖啡添加了不同的调料,咖啡的价格需要做出相应的改变。 要求ÿ…...
《golang设计模式》第一部分·创建型模式-01-单例模式(Singleton)
文章目录 1. 概述1.1 目的1.2 实现方式 2. 代码示例2.1 设计2.2 代码 1. 概述 1.1 目的 保证类只有一个实例有方法能让外部访问到该实例 1.2 实现方式 懒汉式 在第一次调用单例对象时创建该对象,这样可以避免不必要的资源浪费 饿汉式 在程序启动时就创建单例对象…...
若依微服务整合activiti7.1.0.M6
若依微服务3.6.3版本整合activiti7(7.1.0.M6) 目前有两种办法集成activiti7 放弃activiti7新版本封装的API,使用老版本的API,这种方式只需要直接集成即可,在7.1.0.M6版本中甚至不需要去除security的依赖。不多介绍&a…...
Ubuntu 下安装软件,卸载,查看已经安装的软件
参考网址:http://wiki.ubuntu.org.cn/UbuntuSkills 一般的安装程序用三种: .deb 和.rpm 这两种安装文件 .bundle 这是二进制的安装文件 而 tar.gz 这类的只是压缩包(相当于 .rar,.zip 压缩包一样),如果此类文件是程序的话&a…...
微信小程序导入微信地址
获取用户收货地址。调起用户编辑收货地址原生界面,并在编辑完成后返回用户选择的地址。 1:原生微信小程序接口使用API:wx.chooseAddress(OBJECT) wx.chooseAddress({success (res) {console.log(res.userName)console.log(res.postalCode)c…...
如何在Debian中配置代理服务器?
开始搭建代理服务器 首先我参考如下文章进行搭建代理服务器,步骤每一个命令都执行过报了各种错,找了博客 目前尚未开始,我已经知道我的路很长,很难走呀,加油,go!go!go! …...
在外远程NAS群晖Drive - 群晖Drive挂载电脑磁盘同步备份【无需公网IP】
文章目录 前言1.群晖Synology Drive套件的安装1.1 安装Synology Drive套件1.2 设置Synology Drive套件1.3 局域网内电脑测试和使用 2.使用cpolar远程访问内网Synology Drive2.1 Cpolar云端设置2.2 Cpolar本地设置2.3 测试和使用 3. 结语 前言 群晖作为专业的数据存储中心&…...
[SQL挖掘机] - 标量子查询
介绍: 标量子查询(Scalar Subquery)是一种特殊类型的子查询,它返回单个值作为结果,而不是结果集。标量子查询通常嵌套在另一个查询的选择列表、条件或表达式中,并提供单个值来完成计算、比较或作为查询结果的一部分。…...
MTK 进META的两种方式
1. Preloader进meta: 开机情况下:先发adb reboot meta进入Preloader,然后再进META 2. 开机后直接进meta...
AutoSAR系列讲解(实践篇)9.2-信息发送的Filter机制
再强调一遍哈,这几节的内容大家看不懂没关系。都属于不常用的知识,仅作了解,假如用到了可以再挖出来看看。还有一点,很多的英文不太好翻译,比如这里的Filter,翻译成滤波,筛选我感觉都不太贴切,干脆就直接叫Filter了,之后应该会出现类似的英文,博主尽量想办法让大家理…...
JVM详解(超详细)
目录 JVM 的简介 JVM 执行流程 JVM 运行时数据区 由五部分组成 JVM 的类加载机制 类加载的过程(五个) 双亲委派模型 类加载器 双亲委派模型的优点 JVM 中的垃圾回收策略 GC GC 中主要分成两个阶段 死亡对象的判断算法 引用计数算法 可达性分析算法 垃圾回收算…...
Vue学习Day3——生命周期\组件化
一、Vue生命周期 Vue生命周期:就是一个Vue实例从创建 到 销毁 的整个过程。 生命周期四个阶段:① 创建 ② 挂载 ③ 更新 ④ 销毁 1.创建阶段:创建响应式数据 2.挂载阶段:渲染模板 3.更新阶段:修改数据,更…...
Rust vs Go:常用语法对比(八)
题目来自 Golang vs. Rust: Which Programming Language To Choose in 2023?[1] 141. Iterate in sequence over two lists Iterate in sequence over the elements of the list items1 then items2. For each iteration print the element. 依次迭代两个列表 依次迭代列表项1…...
pytorch学习-线性神经网络——softmax回归+损失函数+图片分类数据集
1.softmax回归 Softmax回归(Softmax Regression)是一种常见的多分类模型,可以用于将输入变量映射到多个类别的概率分布中。softmax回归是机器学习中非常重要并且经典的模型,虽然叫回归,实际上是一个分类问题 1.1分类与…...
Docker compose(容器编排)
Docker compose(容器编排) 一、安装Docker compose 1.安装Docker compose Docker Compose 环境安装 Docker Compose 是 Docker 的独立产品,因此需要安装 Docker 之后在单独安装 Docker Compose#下载 curl -L https://github.com/docker/co…...
xmind latex【记录备忘】
xmind latex 换行 换行必须要有\begin{align}和\end{align},此时再在里面用\才能换行,如果只写112\224是不能换行的...
RocketMQ(1.NameServer源码)
NameServer功能简述 主要功能如下 服务注册与发现:Nameserver扮演了RocketMQ集群中服务注册中心的角色。当RocketMQ中的Broker、Producer和Consumer启动时,它们会向Nameserver注册自己的网络地址和角色信息。Nameserver维护着集群中所有活跃实例的信息…...
责任链vs金融登录
金融app相对普通app而言,出于安全考虑,其安全校验方式比较多,以某些银行app为例,手机号登录成功后,会增加指纹、手势、OCR人脸等验证!这些安全项的校验,会根据用户的风险等级有不同的校验优先级…...
通过VIOOVI,了解联合作业分析的意义和目标!
现如今企业的主流生产模式就是流水线生产,一道工序结束后,紧接着开展下一项工序,这种作业模式可以以一种比较高效的方式缩减生产时间。尽管流水作业的效率已经够高的了,但是各个工序之间如果衔接不到位的话,会造成生产…...
清洁机器人规划控制方案
清洁机器人规划控制方案 作者联系方式Forrest709335543qq.com 文章目录 清洁机器人规划控制方案方案简介方案设计模块链路坐标变换算法框架 功能设计定点自主导航固定路线清洁区域覆盖清洁贴边沿墙清洁自主返航回充 仿真测试仿真测试准备定点自主导航测试固定路线清洁测试区域…...
【人工智能】神经网络的优化器optimizer(二):Adagrad自适应学习率优化器
一.自适应梯度算法Adagrad概述 Adagrad(Adaptive Gradient Algorithm)是一种自适应学习率的优化算法,由Duchi等人在2011年提出。其核心思想是针对不同参数自动调整学习率,适合处理稀疏数据和不同参数梯度差异较大的场景。Adagrad通…...
为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?
在建筑行业,项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升,传统的管理模式已经难以满足现代工程的需求。过去,许多企业依赖手工记录、口头沟通和分散的信息管理,导致效率低下、成本失控、风险频发。例如&#…...
2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...
短视频矩阵系统文案创作功能开发实践,定制化开发
在短视频行业迅猛发展的当下,企业和个人创作者为了扩大影响力、提升传播效果,纷纷采用短视频矩阵运营策略,同时管理多个平台、多个账号的内容发布。然而,频繁的文案创作需求让运营者疲于应对,如何高效产出高质量文案成…...
Python基于历史模拟方法实现投资组合风险管理的VaR与ES模型项目实战
说明:这是一个机器学习实战项目(附带数据代码文档),如需数据代码文档可以直接到文章最后关注获取。 1.项目背景 在金融市场日益复杂和波动加剧的背景下,风险管理成为金融机构和个人投资者关注的核心议题之一。VaR&…...
Linux 中如何提取压缩文件 ?
Linux 是一种流行的开源操作系统,它提供了许多工具来管理、压缩和解压缩文件。压缩文件有助于节省存储空间,使数据传输更快。本指南将向您展示如何在 Linux 中提取不同类型的压缩文件。 1. Unpacking ZIP Files ZIP 文件是非常常见的,要在 …...
Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案
在大数据时代,海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构,在处理大规模数据抓取任务时展现出强大的能力。然而,随着业务规模的不断扩大和数据抓取需求的日益复杂,传统…...
系统掌握PyTorch:图解张量、Autograd、DataLoader、nn.Module与实战模型
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文通过代码驱动的方式,系统讲解PyTorch核心概念和实战技巧,涵盖张量操作、自动微分、数据加载、模型构建和训练全流程&#…...
HTML前端开发:JavaScript 获取元素方法详解
作为前端开发者,高效获取 DOM 元素是必备技能。以下是 JS 中核心的获取元素方法,分为两大系列: 一、getElementBy... 系列 传统方法,直接通过 DOM 接口访问,返回动态集合(元素变化会实时更新)。…...
