Head First设计模式---3.装饰者模式
3.1装饰者模式
亦称: 装饰者模式、装饰器模式、Wrapper、Decorator
装饰模式是一种结构型设计模式, 允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。
举个例子:天气很冷,我们一件一件穿衣服,从内到外,穿了很多件衣服。
问题
欢迎来到瑞心咖啡店,本店以迅速的扩张优势占领了附近的几条街,但是随着顾客的增多,我们需要一套更完善和流程的架构去应对咖啡的销售,所以我们需要更新我们的订单系统!
我们原先的类设计是这样的:
- 每种咖啡不同,价格也不同
- 消费者可以根据不同的咖啡选择加入不同的小料:摩卡、豆浆等等,或者覆盖奶泡
- 当然有的咖啡加入了某些小料之后会变得异常难喝
瑞心咖啡会个根据不同的小料收取不同的费用。所以订单系统必须考虑到这些事情。我们继续使用这样的类型,使我们的结构变得异常臃肿:
当每上市一种新的调料或者咖啡,我们就陷入了一种麻烦的循环中去。。。。
解决方案
我们可以使用继承和实例变量来追踪这些调料
我们先从Beverage
下手,加上一些实例变量来代表是否加调料,然后加上cost()
方法来计算所需要的价格。对不同的咖啡继承对应的Beverage
类,其中不包含任何小料,当需要小料时,只需要将小料加入进来即可。那么当然小料也需要一个类去实现。
那么对于我们的设计来说,需要遵循一个设计原则:开闭原则
开闭原则:对扩展开发,对修改关闭
我们可以对类进行扩展,但是就不用更改已经有的实现
所以如果我们有以下需求:
- 点一杯Espresso(意大利浓咖啡)
- 加一份摩卡
- 加一份牛奶
那么根据我们的装饰者模式来说,最后包装的样子是不是这样:
所以当我们是算钱的时候,是不是一层一层的委托给外面,就可以实现了呢?
好了我们已经了解了装饰者模式,让我们看实现的代码:
- Beverage类,抽象出来,作为咖啡的和调味品的基类,用于算钱和描述
public abstract class Beverage {/*** 描述*/public String description = "Unknown Beverage";/*** 返回咖啡描述* @return 描述*/public String getDescription(){return description;}/*** 购买该描述需要花费的*/public abstract double cost();
}
- DarkRoast咖啡类
public class DarkRoast extends Beverage {public DarkRoast(){description = "DarkRoast";}@Overridepublic double cost() {return 1.69;}
}
- Espresso咖啡类
public class Espresso extends Beverage {/*** 咖啡类*/public Espresso(){description = "Espresso";}@Overridepublic double cost() {return 1.99;}
}
-
HouseBlend类,和Espresso、DarkRoast一样,不再写
-
CondimentDecorator调味品类,用于描述咖啡所加的内容
public abstract class CondimentDecorator extends Beverage {/*** 获取描述*/public abstract String getDescription();
}
- 调味品的实现类:Mocha类
public class Mocha extends CondimentDecorator {/*** 不同的咖啡作为参数加入进来*/Beverage beverage;public Mocha(Beverage beverage){this.beverage = beverage;}/*** 计算价格* @return 该调味品需要的钱 + coffee需要的钱*/@Overridepublic double cost() {return 0.20 + beverage.cost();}@Overridepublic String getDescription() {return beverage.getDescription() + ",Mocha";}
}
-
其他的调味品类(Soy、Whip)同上
-
设计好了之后,我们开始调试我们的包装者吧!
public class Main {public static void main(String[] args) {Beverage beverage = new Espresso();System.out.println("Beverage:"+beverage.getDescription());//DarkRost咖啡 添加了Mocha * 2 , whip * 1Beverage beverage1 = new DarkRoast();beverage1 = new Mocha(beverage1);beverage1 = new Mocha(beverage1);beverage1 = new Whip(beverage1);System.out.println("DarkRoast:"+beverage1.getDescription() + " cost: $" + beverage1.cost());//HouseBlen咖啡 添加了Soy、Mocha、Whip * 1Beverage beverage2 = new HouseBlend();beverage2 = new Soy(beverage2);beverage2 = new Mocha(beverage2);beverage2 = new Whip(beverage2);System.out.println("HouseBlend:"+beverage2.getDescription() + " cost: $" + beverage2.cost());}
}
运行结果:
Beverage:Espresso
DarkRoast:DarkRoast,Mocha,Mocha,whip cost: $2.5999999999999996
HouseBlend:House Blend Coffee,soy,Mocha,whip cost: $2.02
装饰模式结构
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gGFdizP6-1676982856417)(null)]
- 部件 (Component) 声明封装器和被封装对象的公用接口。
- 具体部件 (Concrete Component) 类是被封装对象所属的类。 它定义了基础行为, 但装饰类可以改变这些行为。
- 基础装饰 (Base Decorator) 类拥有一个指向被封装对象的引用成员变量。 该变量的类型应当被声明为通用部件接口, 这样它就可以引用具体的部件和装饰。 装饰基类会将所有操作委派给被封装的对象。
- 具体装饰类 (Concrete Decorators) 定义了可动态添加到部件的额外行为。 具体装饰类会重写装饰基类的方法, 并在调用父类方法之前或之后进行额外的行为。
- 客户端 (Client) 可以使用多层装饰来封装部件, 只要它能使用通用接口与所有对象互动即可。
装饰模式适合应用场景
如果你希望在无需修改代码的情况下即可使用对象, 且希望在运行时为对象新增额外的行为, 可以使用装饰模式。
装饰能将业务逻辑组织为层次结构, 你可为各层创建一个装饰, 在运行时将各种不同逻辑组合成对象。 由于这些对象都遵循通用接口, 客户端代码能以相同的方式使用这些对象。
如果用继承来扩展对象行为的方案难以实现或者根本不可行, 你可以使用该模式。
许多编程语言使用 final
最终关键字来限制对某个类的进一步扩展。 复用最终类已有行为的唯一方法是使用装饰模式: 用封装器对其进行封装。
实现方式
- 确保业务逻辑可用一个基本组件及多个额外可选层次表示。
- 找出基本组件和可选层次的通用方法。 创建一个组件接口并在其中声明这些方法。
- 创建一个具体组件类, 并定义其基础行为。
- 创建装饰基类, 使用一个成员变量存储指向被封装对象的引用。 该成员变量必须被声明为组件接口类型, 从而能在运行时连接具体组件和装饰。 装饰基类必须将所有工作委派给被封装的对象。
- 确保所有类实现组件接口。
- 将装饰基类扩展为具体装饰。 具体装饰必须在调用父类方法 (总是委派给被封装对象) 之前或之后执行自身的行为。
- 客户端代码负责创建装饰并将其组合成客户端所需的形式。
装饰模式优缺点
优点:
- 你无需创建新子类即可扩展对象的行为。
- 你可以在运行时添加或删除对象的功能。
- 你可以用多个装饰封装对象来组合几种行为。
- 单一职责原则。 你可以将实现了许多不同行为的一个大类拆分为多个较小的类。
缺点:
- 在封装器栈中删除特定封装器比较困难。
- 实现行为不受装饰栈顺序影响的装饰比较困难。
- 各层的初始化配置代码看上去可能会很糟糕。
与其他模式的关系
-
适配器模式可以对已有对象的接口进行修改, 装饰模式则能在不改变对象接口的前提下强化对象功能。 此外, 装饰还支持递归组合, 适配器则无法实现。
-
适配器能为被封装对象提供不同的接口, 代理模式能为对象提供相同的接口, 装饰则能为对象提供加强的接口。
-
责任链模式和装饰模式的类结构非常相似。 两者都依赖递归组合将需要执行的操作传递给一系列对象。 但是, 两者有几点重要的不同之处。
责任链的管理者可以相互独立地执行一切操作, 还可以随时停止传递请求。 另一方面, 各种装饰可以在遵循基本接口的情况下扩展对象的行为。 此外, 装饰无法中断请求的传递。
-
组合模式和装饰的结构图很相似, 因为两者都依赖递归组合来组织无限数量的对象。
装饰类似于组合, 但其只有一个子组件。 此外还有一个明显不同: 装饰为被封装对象添加了额外的职责, 组合仅对其子节点的结果进行了 “求和”。
但是, 模式也可以相互合作: 你可以使用装饰来扩展组合树中特定对象的行为。
-
大量使用组合和装饰的设计通常可从对于原型模式的使用中获益。 你可以通过该模式来复制复杂结构, 而非从零开始重新构造。
-
装饰可让你更改对象的外表, 策略模式则让你能够改变其本质。
-
装饰和代理有着相似的结构, 但是其意图却非常不同。 这两个模式的构建都基于组合原则, 也就是说一个对象应该将部分工作委派给另一个对象。 两者之间的不同之处在于代理通常自行管理其服务对象的生命周期, 而装饰的生成则总是由客户端进行控制。
参考:First Head设计模式、设计模式
相关文章:

Head First设计模式---3.装饰者模式
3.1装饰者模式 亦称: 装饰者模式、装饰器模式、Wrapper、Decorator 装饰模式是一种结构型设计模式, 允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。 举个例子:天气很冷,我们一件一件穿衣服,…...

Python 算法交易实验48 表字段设计
说明 虽然说的是表,实际上用的是Mongo集合 基于ADBS(APIFunc DataBase Service)可以构造一个供后续研究、生产长时间使用的数据基础,这个基础包括了: 1 队列服务。通过队列,数据可以通过API实现零担和批量两种模式的快速存储。2 …...

库存管理系统-课后程序(JAVA基础案例教程-黑马程序员编著-第六章-课后作业)
【案例6-1】 库存管理系统 【案例介绍】 1.任务描述 像商城和超市这样的地方,都需要有自己的库房,并且库房商品的库存变化有专人记录,这样才能保证商城和超市正常运转。 本例要求编写一个程序,模拟库存管理系统。该系统主要包…...

【极海APM32替代笔记】HAL库低功耗STOP停止模式的串口唤醒(解决进入以后立马唤醒、串口唤醒和回调无法一起使用、接收数据不全的问题)
【极海APM32替代笔记】HAL库低功耗STOP停止模式的串口唤醒(解决进入以后立马唤醒、串口唤醒和回调无法一起使用、接收数据不全的问题) 【STM32笔记】低功耗模式配置及避坑汇总 前文: blog.csdn.net/weixin_53403301/article/details/128216…...
Python类变量和实例变量(类属性和实例属性)
无论是类属性还是类方法,都无法像普通变量或者函数那样,在类的外部直接使用它们。我们可以将类看做一个独立的空间,则类属性其实就是在类体中定义的变量,类方法是在类体中定义的函数。 在类体中,根据变量定义的位置不…...
Glide加载图片
使用Glide加载图片,默认情况下在内存中缓存该图片。这样的情况下如果我们保存头像在某个路径,当再次更换头像时可能由于缓存问题,UI上更新的不及时。 默认加载图片方式: Glide.with(context).load(coverPath).error(R.drawable.a…...
有关时间复杂度和空间复杂度的练习
目录 一、消失的数字 二、轮转数组 三、 单选题 一、消失的数字 数组nums包含从0到n的所有整数,但其中缺了一个。请编写代码找出那个缺失的整数。你有办法在 O(n) 时间内完成吗? 注意:本题相对书上原题稍作改动 示例 1: 输入…...
linux服务器nfs数据挂载
参考:https://blog.csdn.net/qq_43721935/article/details/119889841?from_wecom1 1、NFS 介绍 NFS 即网络文件系统(Network File-System),可以通过网络让不同机器、不同系统之间可以实现文件共享。通过 NFS,可以访问…...

Python 自动化测试必会技能板块—unittest框架
说到 Python 的单元测试框架,想必接触过 Python 的朋友脑袋里第一个想到的就是 unittest。的确,作为 Python 的标准库,它很优秀,并被广泛应用于各个项目。但其实在 Python 众多项目中,主流的单元测试框架远不止这一个。…...

mysql存储引擎、事务、索引
目录MySQL进阶存储引擎什么是存储引擎常用存储引擎事务什么是事务怎么理解提交事务 和回滚事务事务特性事务的隔离级别索引什么是索引索引的实现原理什么条件下,我们会考虑给字段添加索引呢?什么条件下,索引会失效?索引分类MySQL进阶 存储引…...

毕业论文图片格式、分辨率选择及高质量Word转PDF方法
已知1:毕业论文盲评通常需要提交PDF文件。 已知2:PDF文件太大可能会导致翻页卡顿以及上传盲评网站失败。 已知3:Word转PDF方法不当可能会导致图像模糊。 已知4:打印机分辨率通常为300dpi。 问题1:论文插图分辨率设置…...

华为外包测试2年,不甘被替换,168天的学习转岗成正式员工
我25岁的时候,华为外包测试,薪资13.5k,人在深圳。 内卷什么的就不说了,而且人在外包那些高级精英年薪大几十的咱也接触不到,就说说外包吧。假设以我为界限,25岁一线城市13.5k,那22-24大部分情况…...

简单的C++:【运算符重载】新手易学
学过C语言的同志们应该都知道位运算符>> 和 << (右移左移),但是这两个运算符在C中还是我们的输入和输出流操作符,那么这是为什么呢?,了解完本篇文章之后,我们再来回答这个问题。 C为…...

NPE:记一次脑残NPE的排查过程
目录 碎碎念: 如下这行报NPE: 排查过程: 解解方案: 小结: 空指针出现的几种情况: 如何从根源避免空指针: 赋值时自动拆箱出现空指针: 1、变量赋值自动拆箱出现的空指针 2、…...

canvas样式与颜色,字体,图片,状态,形变
色彩 fillStyle color 设置图形的填充颜色。 strokeStyle color 设置图形轮廓的颜色。 备注: 一旦您设置了 strokeStyle 或者 fillStyle 的值,那么这个新值就会成为新绘制的图形的默认值。如果你要给每个图形上不同的颜色,你需要重新设置…...

重识html
html 重识html 万维网用url统一资源定位符标识分布因特网上的各种文档 各种概念 URL: 统一资源定位器 它是WWW的统一资源定位标志,就是指网络地址 在WWW上,每一信息资源都有统一的且在网上唯一的地址 网页: 由文字 图片 视频 音乐各种元素排列组…...

Redis:缓存一致性问题(缓存更新策略)
Redis缓存的一致性1. 缓存1.1 缓存的作用:1.2 缓存的成本:2. 缓存模型3. 缓存一致性问题3.1 引入3.2 解决(1) 先更新数据库,再手动删除缓存(2) 使用事务保证原子性(3) 以Redis中的TTL为兜底3.3 案例:商铺信息查询和更新(1) 查询商…...

spring之声明式事务开发
文章目录一、声明式事务之全注解式开发1、新建springConfig类2、测试程序3、测试结果二、声明式事务之XML实现方式1、配置步骤2、测试程序3、运行结果附一、声明式事务之全注解式开发 基于之前的银行转账系统,将spring.xml配置文件嘎掉,变成全注解式开发…...

2023美赛参赛经历分享
今天早上登录MCM: The Mathematical Contest in Modeling (comap.com)发现论文提交已经显示Received。虽然这几天连连有开学恶补的期末考试,但还是忙里偷闲趁着新鲜写一篇关于美赛的参赛个人感受。跟我一起打这次美赛的都是软件等专业的hxd,他们之前没有…...

Elasticsearch在Linux中的单节点部署和集群部署
目录一、Elasticsearch简介二、Linux单节点部署1、软件下载解压2、创建用户3、修改配置文件4、切换到刚刚创建的用户启动软件5、测试三、Linux集群配置1、拷贝文件2、修改配置文件3、分别修改文件所有者4、启动三个软件5、测试四、问题总结1、在elasticsearch启动时如果报错内存…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...

微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...
WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)
一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解,适合用作学习或写简历项目背景说明。 🧠 一、概念简介:Solidity 合约开发 Solidity 是一种专门为 以太坊(Ethereum)平台编写智能合约的高级编…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...
使用Matplotlib创建炫酷的3D散点图:数据可视化的新维度
文章目录 基础实现代码代码解析进阶技巧1. 自定义点的大小和颜色2. 添加图例和样式美化3. 真实数据应用示例实用技巧与注意事项完整示例(带样式)应用场景在数据科学和可视化领域,三维图形能为我们提供更丰富的数据洞察。本文将手把手教你如何使用Python的Matplotlib库创建引…...
【Go语言基础【13】】函数、闭包、方法
文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...

GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...

【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...