【设计模式】装饰器模式
装饰器模式
以生活中的场景来举例,一个蛋糕胚,给它涂上奶油就变成了奶油蛋糕,再加上巧克力和草莓,它就变成了巧克力草莓蛋糕。
像这样在不改变原有对象的基础之上,将功能附加到原始对象上的设计模式就称为装饰模式(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 …...
蓝桥杯集训·每日一题Week1
前缀和(Monday) AcWing 3956. 截断数组(每日一题) 思路: 首先可以预处理出前缀和。判断数组长度如果小于 333 或者前 nnn 项不是 333 的倍数,则可以直接输出 000。 否则就枚举所有 s[i]s[n]3s[i] \cfrac…...
25k的Java开发常问的ThreadLocal问题有哪些?
前言:ThreadLocal问的比较多的是和Synchronized的区别、ThreadLocal被设计弱引用、存储元素的过程、实现线程隔离的原理。 文章目录 ThreadLocalThreadLocal定义ThreadLocal与Synchronized的区别ThreadLocal底层实现ThreadLocalMap存储元素的过程ThreadLocal实现线程隔离的原理…...
R语言基础(四):数据类型
R语言基础(一):注释、变量 R语言基础(二):常用函数 R语言基础(三):运算 5.数据类型 5.1 基本数据类型 R语言基本数据类型大致有六种: 整数Integer、浮点数Numeric、文本(字符串)Character、逻辑(布尔)Logical、复合类型Complex、…...
批处理命令--总结备忘「建议收藏」
批处理命令--总结备忘「建议收藏」 前言1、基础语法:2、批处理基本命令3、实例3.1 打开虚拟目录3.2 以当前时间为文件名,建文件夹3.3 备份postgresql数据库前言 最近用批处理命令做了一些postgresql数据库的备份,打开虚拟环境。。。发现批处理处理一些常用重复工作时真的很…...
面试知识点梳理及相关面试题(十一)-- docker
1. Docker和虚拟机的区别 容器不需要捆绑一整套操作系统,它只需要满足软件运行的最小内核就行了。 传统虚拟机技术是虚拟出一整套硬件后,在其上运行一个完成操作系统,在该系统上再运行所需应用进程容器内的应用进程直接运行于宿主的内核&am…...
k8s--services(微服务)
文章目录一、k8s网络通信service和iptables的关系二、services1.简介2.默认3.IPVS模式的service4.clusterip5.headless6.从外部访问service的三种方式(1)nodeport(2)loadbalancer7.metallb一、k8s网络通信 k8s通过CNI接口接入其他…...
【Java开发】设计模式 01:单例模式
1 单例模式介绍单例模式(Singleton Pattern)是Java中最为基础的设计模式。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对…...
10、go工程化与标准库
目录一、用go mod管理工程二、包引入规则三、init调用链四、可见性五、标准库1 - 时间函数2 - 数学计算3 - I/O操作4 - 编码一、用go mod管理工程 初始化项目:go mod init $module_name,$module_name和目录名可以不一样。上述命令会生成go.mod文件 mod…...
【Selenium自动化测试】鼠标与键盘操作
在 WebDriver 中,与鼠标操作相关的方法都封装在ActionChains 类中,与键盘操作相关的方法都封装在Keys类中。下面介绍下这两个类中的常用方法。 鼠标操作 ActionChains类鼠标操作常用方法: context_click():右击double_click()&…...
自定义javax.validation校验枚举类
枚举类单一情况 package com.archermind.cloud.phone.dto.portal.external.validation.validator;import com.archermind.cloud.phone.dto.portal.external.validation.constraints.EnumValidation; import lombok.extern.slf4j.Slf4j;import javax.validation.ConstraintVali…...
网站建设及解析流程/软文网站大全
注意编码一致性,如:我的数据库是utf-8编码,csv文档的编码也是utf-8. 导入使用 LOAD DATA LOCAL INFILE 详细用法请参看文档或者书籍。 mysql user表结构: ----------------------------------------------------------- | Field | Type …...
坪地网站建设如何/网站批量收录
公众号关注 「奇妙的 Linux 世界」设为「星标」,每天带你提升技术视野!私有云裸金属架构(这是相对云上环境来说,不是说无操作系统)上部署的 Kubernetes 集群,通常是无法使用 LoadBalancer 类型的 Service 的…...
阿里云的wordpress/优秀营销软文范例300字
先调用 api 在public 下的index.html中 引入这个支付宝人脸api 第一个是手机打印的api 也就是手机上的console <script type"text/javascript" src"//portal.zjzwfw.gov.cn/assets/jsbridge/1.0.0/jsbridge.js"></script> <script type&q…...
网站 引导页 设计/30个免费货源网站
本文实例讲述了PHP使用curl_multi_select解决curl_multi网页假死问题的方法。分享给大家供大家参考,具体如下:curl_multi可以批处理事务,给网页编程带来很大的方便。不过在使用curl_multi的过程中,我们会遇到一个比较头疼的问题&a…...
wap网站源码.net/公众号怎么推广和引流
js写了也有两年多了,大到复杂的后台系统,小到页面,还有日均300万的网页主站,HTML5的适配页面等等。 框架也杂七杂八接触了不少,从小的jquery、bootstrap、echarts等等,到大一些的框架Vue、San、ERÿ…...
网站ftp查询/软文案例200字
粘包 半包概念 粘包:客户端发送的多条数据在服务器上在一个报文中收到了。 半包:客户端发送的数据很大,所以需要分段发送。 产生原因 由于tcp是流式发送数据,在遇到大文件的时候不可能整个发送过去,所以就需要分段…...