【设计模式】装饰器模式
装饰器模式
以生活中的场景来举例,一个蛋糕胚,给它涂上奶油就变成了奶油蛋糕,再加上巧克力和草莓,它就变成了巧克力草莓蛋糕。
像这样在不改变原有对象的基础之上,将功能附加到原始对象上的设计模式就称为装饰模式(Decorator模式),属于结构型模式。
装饰器模式中主要有四个角色:
Component : 定义被装饰对象的接口,装饰器也需要实现一样的接口
ConcreteComponent: 具体的装饰对象,实现了Component接口,通常就是被装饰的原始对象
Decorator: 所有装饰器的抽象父类,需要定义与Component一致的接口,并且持有一个被装饰的Component对象
ConcreteDecorator: 具体装饰器对象

代码示例
假设我们需要设计一个奖金的系统,目前一个工作人员的奖金包含三部分:
本月销售额的奖金 : 当月销售额的3%
累计销售额的奖金 : 累计销售额的0.1%
团队销售额的奖金 : 团队销售额的1%,只有经理才有
使用装饰模式的实现如下 : 定义一个所有奖金的抽象接口,然后定义一个BasicPrize的初始奖金对象,不同的人奖金的组成不同,就为其添加不同的装饰器
public abstract class Component {abstract double calPrize(String userName);
}
public class BasicPrize extends Component {//保存了一个员工与销售额的对应关系的mappublic static Map<String,Double> saleMoney = new HashMap<String,Double>();static {saleMoney.put("小明",9000.0);saleMoney.put("小陈",20000.0);saleMoney.put("小王",30000.0);saleMoney.put("张经理",55000.0);}public double calPrize(String userName) {return 0;}
}
/**** 所有装饰器的抽象父类,持有一个被装饰对象*/
public abstract class Decorator extends Component {protected Component component;public Decorator(Component component){this.component = component;}public abstract double calPrize(String userName);
}
/*** * 当月奖金计算规则*/
public class MonthPrizeDecorator extends Decorator{public MonthPrizeDecorator(Component component) {super(component);}public double calPrize(String userName) {//先计算被装饰对象的奖金(就是在加上本奖金之前的奖金)double money = this.component.calPrize(userName);//计算本奖金 对应员工的业务额的3%double prize = BasicPrize.saleMoney.get(userName)*0.03;System.out.println(userName+"当月 业务奖金:"+prize);return money + prize;}}
/*** 累计奖金*/
public class SumPrizeDecorator extends Decorator {public SumPrizeDecorator(Component component) {super(component);}public double calPrize(String userName) {//先计算被装饰对象的奖金(就是在加上本奖金之前的奖金)double money = this.component.calPrize(userName);//计算本奖金 累计业务额的0.1% 假设是100000double prize = 100000*0.001;System.out.println(userName+"当月 累计奖金:"+prize);return money + prize;}}
/*** 团队奖金*/
public class GroupPrizeDecorator extends Decorator {public GroupPrizeDecorator(Component component) {super(component);}public double calPrize(String userName) {//先计算被装饰对象的奖金(就是在加上本奖金之前的奖金)double money = this.component.calPrize(userName);//计算本奖金 本团队的业务额的1%double sumSale = 0;for(double sale: BasicPrize.saleMoney.values()){sumSale += sale;}double prize = sumSale*0.01;System.out.println(userName+"当月 团队奖金:"+prize);return money + prize;}}
/*** @Description*/
public class Client {public static void main(String[] args) throws FileNotFoundException {//基础对象 奖金0Component basePrice = new BasicPrize();//当月奖金Component salePrize = new MonthPrizeDecorator(basePrice);//累计奖金Component sumPrize = new SumPrizeDecorator(salePrize);//团队奖金Component groupPrize = new GroupPrizeDecorator(sumPrize);//普通员工的奖金由两部分组成double d1 = sumPrize.calPrize("小明");System.out.println("========================小明总奖金:"+d1);double d2 = sumPrize.calPrize("小陈");System.out.println("========================小陈总奖金:"+d2);double d3 = sumPrize.calPrize("小王");System.out.println("========================小王总奖金:"+d3);//王经理的奖金由三部分组成double d4 = groupPrize.calPrize("张经理");System.out.println("========================张经理总奖金:"+d4);}
}
输出结果如下:

这里我们使用装饰器模式,主要是为了方便组合和复用。在一个继承的体系中,子类往往是互斥的,比方在一个奶茶店,它会有丝袜奶茶,红茶,果茶等,用户想要一杯饮料,一般都会在这些种类中选一种,不能一杯饮料既是果茶又是奶茶。然后用户可以根据自己的喜好添加任何想要的decorators,珍珠,椰果,布丁等,这些添加物对所有茶类饮品都是相互兼容的,并且是可以被允许反复添加的(同样的装饰器是否允许在同一个对象上装饰多次,视情况而定,像上面的奖金场景显然是不被允许的)
jdk中的装饰器模式
java.io包是用于输入输出的包,这里使用了大量的装饰器模式,我们再来体会一下装饰器模式的优点。
下图是jdk 输出流的一部分类图,很明显是一个装饰模式的类图,OutputStream是顶层父类,FileOutputStream和ObjectOutputStream是具体的被装饰类,FilterOutputStream是所有输出流装饰器的抽象父类

使用的代码如下:
InputStream in = new FileInputStream("/user/wangzheng/test.txt");InputStream bin = new BufferedInputStream(in);byte[] data = new byte[128];while (bin.read(data) != -1) {//...}
为什么Java IO设计的时候要使用装饰器模式, 而J不设计⼀个继承 FileInputStream 并且⽀持缓存的 BufferedFileInputStream 类呢?
如果InputStream 只有⼀个⼦类 FileInputStream 的话,那我们在 FileInputStream 基础之上,再设计⼀个孙⼦类BufferedFileInputStream,也是可以接受的。但实际上,继承 InputStream 的⼦类有很多。我们需要给每⼀个 InputStream 的⼦类,
再继续派⽣⽀持缓存读取的⼦类。
除此之外,我们还需要对功能进⾏其他⽅⾯的增强,⽐如下⾯的DataInputStream 类,⽀持按照基本数据类型(int、boolean、long 等)来读取数据。这种情形下,使用继承的方式的话类的继承结构变得⽆⽐复杂,代码维护起来也比较费劲。
按照装饰器模式的结构,我们可以继承FilterOutputStream实现自定义的装饰器,并且在使用的时候可以和jdk自带的装饰器对象任意组合。
我们可以实现一个简单的复制输出内容的OutputStream装饰器
public class DuplicateOutputStream2 extends FilterOutputStream {/*** Creates an output stream filter built on top of the specified* underlying output stream.** @param out the underlying output stream to be assigned to* the field <tt>this.out</tt> for later use, or* <code>null</code> if this instance is to be* created without an underlying stream.*/public DuplicateOutputStream2(OutputStream out) {super(out);}//将所有的内容复制一份输出 ab 变成aabbpublic void write(int b) throws IOException {super.write(b);super.write(b);}
}
装饰器类是否可以直接实现Component父类?
以输出流为例,如果直接继承OutputStream来实现自定义装饰器
public class DuplicateOutputStream extends OutputStream {private OutputStream os;public DuplicateOutputStream(OutputStream os){this.os = os;}//将所有的内容复制一份输出 ab 变成aabbpublic void write(int b) throws IOException {os.write(b);os.write(b);}
}
public class ClientTest {public static void main(String[] args) throws IOException {DataOutputStream dataOutputStream = new DataOutputStream(new BufferedOutputStream(new DuplicateOutputStream2(new FileOutputStream("1.txt"))));testOutputStream(dataOutputStream);DataOutputStream dataOutputStream1 = new DataOutputStream(new BufferedOutputStream(new DuplicateOutputStream(new FileOutputStream("1.txt"))));testOutputStream(dataOutputStream1);}public static void testOutputStream(DataOutputStream dataOutputStream) throws IOException {DataInputStream dataInputStream = new DataInputStream(new FileInputStream("1.txt"));dataOutputStream.write("bdsaq".getBytes());dataOutputStream.close();System.out.println(dataInputStream.available());byte[] bytes3 = new byte[dataInputStream.available()];dataInputStream.read(bytes3);System.out.println("文件内容:"+new String(bytes3));}
}
输出结果:

乍一看好像没什么区别,但是如果把BufferedOutputStream装饰器和自定义的装饰器互换。
DataOutputStream dataOutputStream2 = new DataOutputStream(new DuplicateOutputStream2(new BufferedOutputStream(new FileOutputStream("1.txt"))));
testOutputStream(dataOutputStream2);DataOutputStream dataOutputStream3 = new DataOutputStream(new DuplicateOutputStream(new BufferedOutputStream(new FileOutputStream("1.txt"))));
testOutputStream(dataOutputStream3);
输出结果:

使用了实现OutputStream的DuplicateOutputStream会出现没有正常输出数据,这是因为我们使用了BufferedOutputStream这个带缓存区的输出流,缓存区的输出流在缓存区没有满的情形下是不会进行输出操作的。一般情形下我们在调用jdk的DataOutputStream的close方法的时候会调用其传入的输出流的flush()方法,并且向下传递调用,BufferedOutputStream里的数据会正常输出。
在使用DuplicateOutputStream2的时候其调用关系是这样的:
dataOutputStream.close()–>duplicateOutputStream2.flush()–>bufferedOutputStream.flush()
使用DuplicateOutputStream的时候由于DuplicateOutputStream继承的OutputStream的flush()方法是空实现,所以不会继续往下调用bufferedOutputStream的flush()方法,故而最后没有得到输出内容
所以装饰器类需要继承Decorator抽象父类,而不是直接继承Component抽象类,我认为是为了在Decorator里实现一些共性的代码,以便在使用装饰器的时候能够更加自由,无视其组合顺序 (当然如果你的Decorator里没有任何逻辑代码,在合适的场景下你可以不定义抽象装饰器类)
总结
装饰器模式主要用于解决继承关系过于复杂的问题,通过组合来替代继承。
它主要的作⽤是给原始类添加增强功能。
除此之外,装饰器模式还有⼀个特点,那就是可以对原始类嵌套使⽤多个装饰器。为了满⾜这个应⽤场景,在设计的时候,装饰器类需要跟原始类继承相同的抽象类或者接⼝。
相关文章:
【设计模式】装饰器模式
装饰器模式 以生活中的场景来举例,一个蛋糕胚,给它涂上奶油就变成了奶油蛋糕,再加上巧克力和草莓,它就变成了巧克力草莓蛋糕。 像这样在不改变原有对象的基础之上,将功能附加到原始对象上的设计模式就称为装饰模式(D…...
Nginx配置实例-反向代理案例一
实现效果:使用nginx反向代理,访问 www.suke.com 直接跳转到本机地址127.0.0.1:8080 一、准备工作 Centos7 安装 Nginxhttps://liush.blog.csdn.net/article/details/125027693 1. 启动一个 tomcat Centos7安装JDK1.8https://liush.blog.csdn.net/arti…...
Java中IO流中字节流(FileInputStream(read、close)、FileOutputStream(write、close、换行写、续写))
IO流:存储和读取数据的解决方案 纯文本文件:Windows自带的记事本打开能读懂 IO流体系: FileInputStream:操作本地文件的字节输入流,可以把本地文件中的数据读取到程序中来 书写步骤:①创建字节输入流对象 …...
C#完全掌握控件之-combbox
无论是QT还是VC,这些可视化编程的工具,掌握好控件的用法是第一步,C#的控件也不例外,尤其这些常用的控件。常见控件中较难的往往是这些与数据源打交道的,比如CombBox、ListBox、ListView、TreeView、DataGridView. 文章…...
STL的空间配置器(allocator)
简答: 在CSTL中,空间配置器便是用来实现内存空间(一般是内存,也可以是硬盘等空间)分配的工具,他与容器联系紧密,每一种容器的空间分配都是通过空间分配器alloctor实现的。 解析: 1.两种C类对象实例化方式的异同在c中&a…...
linux系统莫名其妙的环境变量问题
今天使用Ubuntu20.04系统,使用less命令查看日志,发现日志中的“中文”显示为乱码; 使用vim命令查看该日志文件也显示为乱码; 使用more命令查看该日志文件则显示正常。 首先查询系统的字符集编码,发现编码正常支持中…...
使用 Microsoft Dataverse 简化的连接快速入门
重复昨天本地部署dynamics实例将其所有的包删除之后,再次重新下载回来。运行填写跟之前登陆插件一样的信息点击login 然后查看控制台,出现这样就说明第一个小示例就完成了。查看你的dy365平台下的 “我的活动”就可以看到刚刚通过后台代码创建的东西了。…...
PLSQL Developer 安装指南
PLSQL Developer 是 Oracle 的客户端。 下面以64位破解版的PLSQL Developer为例,进行PLSQL Developer 安装讲解。 0. 下载 PLSQL Developer https://download.csdn.net/download/Shipley_Leo/87557938 1. 根据操作系统选择对应“plsqldev.exe”可执行文件ÿ…...
腾讯云企业网盘2.5版本全新发布啦!!!
腾讯云企业网盘又又又更新啦!本期重点打磨管理协同、企业安全守护能力,同时也不断强化自身产品体验,助力企业高效办公~那么,此次更新具体有什么安全可靠的新功能呢?今天就带大家一起解锁~01协同管理,提升工…...
Excel职业版本(4)
图表 图表基本结构 组成元素 图表的分类 柱状图 介绍:在竖直方向比较不同类型的数据 适用场景:用于二维数据集,对于不同类型的数据进行对比,也可用于同一类型的数据在不同的时间维度的数据对比,通过柱子的高度来反…...
3-2 SpringCloud快速开发入门:Ribbon 实现客户端负载均衡
接上一章节Ribbon 是什么,这里讲讲Ribbon 实现客户端负载均衡 Ribbon 实现客户端负载均衡 由于 Spring Cloud Ribbon 的封装, 我们在微服务架构中使用客户端负载均衡调用非常简单, 只需要如下两步: 1、启动多个服务提供者实例并…...
ChatGPT,乌合之众的疯狂
最近ChatGPT有多火爆就不用我说了。公司里,从CEO到技术人员,乃至于门口的保安、食堂的大婶,没有一个不会聊两句ChatGPT的。连我20年未见的小学同学、三线城市警官,都问我这东西能不能给领导写汇报材料。 用不了多久,家…...
代码随想录刷题-数组-长度最小的子数组
文章目录长度最小的子数组习题暴力解法滑动窗口长度最小的子数组 本节对应代码随想录中:代码随想录,讲解视频:拿下滑动窗口! | LeetCode 209 长度最小的子数组_哔哩哔哩_bilibili 习题 题目链接:209. 长度最小的子数…...
成功解决安装MySQL5.7提示公钥GPG密钥配置为file:///etc/pki/rpm-gpg/RPM-GPG-KEY-mysql
前言 大家好,我是沐风晓月,今天做MySQL5.7安装的时候遇到问题了,我们一起来复盘下这个问题,如果你使用我的方法没有解决,一定要留言给我,我们一起来排查和学习和完善。 本文收录于csdn 我是沐风晓月的专栏 【日常遇到的疑难问题和bug解决】 ,若点击无法跳转,请在csdn …...
vue配置环境变量
目录 创建配置文件 .env.development 文件 .env.production 文件 .env.dev 文件 使用变量 配置 package.json 文件 例子:在 api.js 使用 可以继续添加 创建配置文件 在根目录与 package.json 同级创建文件 .env.development、 .env.production、.env.dev 文件…...
js学习3(数组)
目录 结构图 数组操作 每日一练 结构图 数组操作 ## 数组中可以存储任何类型元素 ## 创建: 字面量([...])、创建对象(new Array(arr_len)) ## 遍历: 循环遍历、forEach(callback)、map(callback)、filter(callback)、every(callback)、some(callback)、…...
不用写代码也能开发,产品经理是怎么做到的?
产品经理再也不用求开发了……就在前几天,我做的小程序上线了! 从产品原型设计,前端开发后端开发,产品部署到运维,都是由我1个人完成的。 我是啥时候学会写代码的呢?不瞒你说,我一行代码都没写…...
Android源码分析 - Parcel 与 Parcelable
0. 相关分享 Android-全面理解Binder原理 Android特别的数据结构(二)ArrayMap源码解析 1. 序列化 - Parcelable和Serializable的关系 如果我们需要传递一个Java对象,通常需要对其进行序列化,通过内核进行数据转发,…...
数字孪生与 UWB 技术创新融合:从单点测量到全局智能化
人员定位是指利用各种定位技术对人员在特定场所的位置进行准确定位的技术。人员定位技术主要应用于需要实时监控、管理和保障人员安全的场所,如大型厂区、仓库、医院、学校、商场等。人员定位技术的应用范围非常广泛,例如:-在工厂生产线上&am…...
蓝桥杯嵌入式PWM_IN(打开中断)
1.原理图 2.配置 3.代码 关键函数 HAL_TIM_IC_Start_IT(&htim3,TIM_CHANNEL_1) HAL_TIM_IC_CaptureCallback(TIM_HandTypeDef *htim)//回调函数 HAL_TIM_GET_COUNTER(&htim3) __HAL_TIM_SetCounter(&htim3,0)void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef …...
Xshell远程连接Kali(默认 | 私钥)Note版
前言:xshell远程连接,私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...
MVC 数据库
MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...
从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...
mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包
文章目录 现象:mysql已经安装,但是通过rpm -q 没有找mysql相关的已安装包遇到 rpm 命令找不到已经安装的 MySQL 包时,可能是因为以下几个原因:1.MySQL 不是通过 RPM 包安装的2.RPM 数据库损坏3.使用了不同的包名或路径4.使用其他包…...
SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...
GitHub 趋势日报 (2025年06月06日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...
GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...
实战设计模式之模板方法模式
概述 模板方法模式定义了一个操作中的算法骨架,并将某些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的前提下,重新定义算法中的某些步骤。简单来说,就是在一个方法中定义了要执行的步骤顺序或算法框架,但允许子类…...
