设计模式之装饰模式(2)--有意思的想法
目录
- 背景
- 概述
- 概念
- 角色
- 基本代码分析
- ❀❀花样重难点
- 聚合关系
- 认贼作父和认孙做父
- 客户端的优化及好处
- 继承到设计模式的演变过程
- 总结
背景
这是我第二次写装饰模式,这一次是在上一次的基础上进一步探究装饰模式,这一次有了很多新的感受和想法,也多了很多趣味性的内容,读完一定让你觉得写代码原来这么有意思。还是简单介绍一下装饰模式的概念,如果想了解更基础更详细的内容,可以先读一下我的第一篇装饰模式的博客:设计模式之装饰模式–优雅的增强
概述
概念
装饰模式(Decorator Pattern)是一种结构型设计模式,动态地给一个对象添加一些额外的功能,就增加功能来说,装饰模式比生成子类更为灵活。
这张图今天将反复出现,先来对照概念来一一认识一下这上面的每一个类
角色
在装饰模式中,通常涉及以下几个重要角色(请对照概念和类图):
-
抽象组件(Component)
抽象组件定义了被装饰对象和装饰对象的共同接口。 -
具体组件(Concrete Component)
具体组件是实现了抽象组件接口的类,它是装饰的目标对象(要增加额外职能的类)。 -
抽象装饰器(Decorator)
抽象装饰器是一个包含了与抽象组件相同接口的成员变量,并从抽象组件继承的类。它的职责是在调用原始对象之前或之后,执行额外的操作(增加的额外职能的父类)。 -
具体装饰器(Concrete Decorator)
具体装饰器是实现了抽象装饰器接口的类,它包装了具体组件,并可以在调用前后执行额外的操作(增加的具体的额外职能)。
总结一下:这种图昨天的类是要增加额外职能的类,右边一坨是要增加的额外职能。
打个比方,左边的 具体组件(Concrete Component)和右边要增加的职能抽象装饰器(Decorator)本来毫不相干,但是现在非要把它们往一起凑(非要变成一家人),怎么办,那就找了一个义父(抽象组件(Component)),强硬的变成了一家人(同一个义父的子类)
基本代码分析
抽象组件:(定义的规范:子类如何实现的规范)
public interface Component {void operation();
}
具体组件:
public class ConcreteComponent implements Component {@Overridepublic void operation() {System.out.println("具体组件的操作");}
}
抽象装饰器:
这是关键类,里面一共三部分,一个Component 类型的属性,一个setComponent方法,用来给属性赋值,这里使用了多态,参数Component component,看起来接受的是一个Component 类型的变量,但是自始至终,接受的只是Component 的子类(ConcreteComponent )和孙类(ConcreteDecoratorA 和ConcreteDecoratorB),用两个词语里形容就是 认贼作父和认孙做父!这个放到后面细说。第三部分是重写的父类方法,调的是传进来的Component类型的变量的operation方法。
public abstract class Decorator implements Component {protected Component component;public void setComponent(Component component) {this.component = component;}@Overridepublic void operation() {if (component != null) {component.operation();}}
}
具体装饰器A:
这两个具体的装饰类继承自Decorator ,同时也继承自Component (双层继承),这里的重点是operation方法中的super.operation();也就是这个方法让整个装饰的过程按照正确顺序串联起来。
ConcreteDecoratorA 中增加了一个属性,也就是给被装饰对象增加的额外职责。
ConcreteDecoratorB中增加了一个方法,给给装饰对象增加的额外职责。
这两个类算是从两个维度,告诉我们如何给被装饰对象增加额外职责的。
public class ConcreteDecoratorA extends Decorator {private String addedProperty;@Overridepublic void operation() {super.operation();addedProperty="New State";System.out.println("具体装饰器A的操作);}}
具体装饰器B:
public class ConcreteDecoratorB extends Decorator {@Overridepublic void operation() {super.operation();addedMethod();}public void addedMethod() {System.out.println("具体装饰器B的操作");}
}
客户端使用:
ConcreteComponent c= new ConcreteComponent();
ConcreteDecoratorA d1= new ConcreteDecoratorA();
ConcreteDecoratorB d2= new ConcreteDecoratorB();d1.setComponent(c);
d2.setComponent(d1);
ConcreteDecoratorB.operation();
客户端里先实例化了三个对象
第一对象C是要装饰的原有对象。
后面两个对象是具体的装饰对象。
重点来了:我来说一下下面代码的执行顺序,不要眨眼**
d1.setComponent ( c ) 这句代码执行的时候会先到ConcreteDecoratorA 类中,没有找到setComponent 方法,因此会向它的父类Decorator 中找,然后把C对象传递给Decorator 的component属性
同样,d2.setComponent(d1)执行的时候,会把装饰了C对象的d1对象传递给Decorator 的component属性。这里一定要注意是装饰了C对象的d1对象。
接下来,执行ConcreteDecoratorB.operation();
会先到ConcreteDecoratorB找到operation方法,这个方法里的第一句是super.operation();
是先执行父类Decorator 的operation方法,这个方法里先判空,然后执行component的operation,这个时候要注意,这个component是刚刚上面传进来的d1,
此时会d1是ConcreteDecoratorA 类型的,那又回到了ConcreteDecoratorA 中执行operation方法,接下来和刚才一样,执行父类Decorator 的operation方法,判空,然后执行component的operation,这个component是刚刚上面传进来的c,c是ConcreteComponent 类型,然后执行ConcreteComponent 里面的operation方法,打印“具体组件的操作”,后面就是一路返回,怎么来的怎么回去。
输出结果为:
具体组件的操作
具体装饰器A的操作
具体装饰器B的操作
❀❀花样重难点
聚合关系
还是这张图
红框框住的两部分,被装饰对象和具体装饰对象加起来是Component (儿子和孙子的抽象),也就是聚合关系的箭头所指,他们加起来聚合到了Decorator ,这是形式上的聚合,在代码中并没有体现(关于这一点还有待探究)
那么为什么不是Decorator自聚合呢,如果是自聚合,只是代表Decorator是具体装饰的容器,那么就不能把服饰包装到ConcreteComponent 上了
关于上面的解释在代码中的体现就是Decorator类中的Component 类型的参数
这里引出来我的下一个问题
认贼作父和认孙做父
Component 本来是Decorator的父类,但是在代码的实际运行中,传的只有ConcreteComponent 和ConcreteDecoratorA 和ConcreteDecoratorB。
ConcreteComponent 相对于Decorator来说是同辈份。(认贼作父)
ConcreteDecoratorA 和ConcreteDecoratorB都是Decorator的子类。(认孙做父)
客户端的优化及好处
这三段代码,可以看到引用都是子类型的,其实完全是可以用父类型去接
先问个问题,这三个实例都可以使用Component去接吗?
不要被迷惑住了,虽然他们都继承自Component,但是Component里可是没有setComponent方法的,所以只有new ConcreteComponent()可以用Component接。
代码可以改成:
Component c= new ConcreteComponent();
Decorator d1= new ConcreteDecoratorA();
Decorator d2= new ConcreteDecoratorB();
这样写有什么好处呢?
多客户端的复用
比如后两句都用Decorator 去接,=号右边完全可以使用配置文件,动态new对象,如果把没一句都看成一个按钮背后的逻辑,每个按钮都是一个客户端,放到页面上就实现了并行。重复的代码就可以使用自动生成。
继承到设计模式的演变过程
最后再说一个我自己的想法,仅供参考
还记得开头概念中说过的“就增加功能来说,装饰模式比生成子类更为灵活”,就先来说说继承,如果我们想给一个类增加功能,如果不在原有类中增加代码的话,可以使用继承的形式增加新的代码,那先看下面的图中左边的图,这个例子是设计模式书上的给人穿衣服的例子,如果不清楚,可以参照我的上一篇博客中的代码,链接在最上面。
加入TShirt是要给Person增加的功能,那么可以使用继承。左边的图。
那么根据封装变化点(这是面向对象的精髓),现在是增加了一个TShirt,我又想增加一个其他的类TShirt2,你会怎么做,会抽出一个父类,遵循依赖倒置原则,会让Person和接口Finery产生关系,就变成了右边的图
同样的道理,现在被装扮类不止一个Person,我也想给Animal装扮。那么就给Person和Animal抽出一个父类Component,遵循依赖倒置原则,会让Finery和接口Component产生关系,就变成了右边的图。现在看这张图和我开篇放的那张装饰模式的图是不是基本一致了,只是缺少了聚合关系。
关于聚合关系,在业务实现过程中并没有体现,这点以后有机会再研究。
总结
装饰模式算比较难的设计模式了,进行了多次学习,每次学习都有不一样的收获,而且越来越有意思,希望今天的文章也能带给你同样的感受。如果你也有有意思的想法和不同意见,欢迎指教,最后感谢阅读。
相关文章:

设计模式之装饰模式(2)--有意思的想法
目录 背景概述概念角色 基本代码分析❀❀花样重难点聚合关系认贼作父和认孙做父客户端的优化及好处继承到设计模式的演变过程 总结 背景 这是我第二次写装饰模式,这一次是在上一次的基础上进一步探究装饰模式,这一次有了很多新的感受和想法,也…...

深入了解 Pinia:现代 Vue 应用的状态管理利器
🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云…...
TTS声音合成:paddlespeech、sherpa-onnx、coqui-ai
1、百度TTS文本合成语音 参考: https://aistudio.baidu.com/aistudio/projectdetail/5237474 https://www.jianshu.com/p/a7522ca6dec4 https://github.com/PaddlePaddle/PaddleSpeech/blob/develop/demos/text_to_speech/README_cn.md 1)过程中需要下载的TTS 声学相关模型…...
Android frameworks 开发总结之十一
1.查看android关机前的log 有时候我们在没有连接电脑的情况下,会在测试的时候突然机器关机. 这个时候如果查看 log信息就看不到了。测试前可以执行下面的命令,之后再进行测试. $ adb shell $ nohup logcat > /sdcard/xxx.log 2.android日期时间同步 关于…...

抑制过拟合——Dropout原理
抑制过拟合——Dropout原理 Dropout的工作原理 实验观察 在机器学习领域,尤其是当我们处理复杂的模型和有限的训练样本时,一个常见的问题是过拟合。简而言之,过拟合发生在模型对训练数据学得太好,以至于它捕捉到了数据中的噪声和…...

开发板启动进入系统以后再挂载 NFS 文件系统, 这里的NFS文件系统是根据正点原子教程制作的ubuntu_rootfs
如果是想开发板启动进入系统以后再挂载 NFS 文件系统,开发板启动进入文件系统,开发板和 ubuntu 能互相 ping 通,在开发板文件系统下新建一个目录 you,然后执行如下指令进行挂载: mkdir mi mount -t nfs -o nolock,nfsv…...
Ubuntu系统执行“docker ps“出现“permission denied“
当我们安装好Ubuntu时,使用鱼香ros一键安装指令 wget http://fishros.com/install -O fishros && . fishros 一键安装Docker后,执行"docker ps"出现"permission denied" seelina:~$ docker ps permission denied while …...
Python与设计模式--桥梁模式
23种计模式之 前言 (5)单例模式、工厂模式、简单工厂模式、抽象工厂模式、建造者模式、原型模式、(7)代理模式、装饰器模式、适配器模式、门面模式、组合模式、享元模式、桥梁模式、(11)策略模式、责任链模式、命令模式、中介者模…...

Linux下查看目录大小
查看目录大小 Linux下查看当前目录大小,可用一下命令: du -h --max-depth1它会从下到大的显示文件的大小。...

鸿蒙原生应用/元服务开发-AGC分发如何下载管理Profile
一、收到通知 尊敬的开发者: 您好,为支撑鸿蒙生态发展,HUAWEI AppGallery Connect已于X月XX日完成存量HarmonyOS应用/元服务的Profile文件更新,更新后Profile文件中已扩展App ID信息;后续上架流程会检测API9以上Harm…...
解决warning: #188-D: enumerated type mixed with another type问题
出现问题处如下, 指示在代码的某处将枚举类型与另一种类型混合使用,这种警告通常在将枚举类型与其他类型进行操作或赋值时出现 enum Mode {MODE_IDLE,MODE_1,MODE_2,MODE_3,MODE_4, }; enum Mode currentMode MODE_IDLE;currentMode (currentMode 1)…...
docker的知识点,以及使用
Docker 是一个开源的应用容器引擎,可以让开发者将应用程序及其依赖项打包至一个可移植的容器中,从而实现快速部署、可扩展和依赖项隔离等特性。下面是 Docker 的一些知识点以及使用方法: Docker 的组成部分包括 Docker 引擎、Docker 镜像、Do…...
WTM(基于Blazor)问题处理记录
问题描述一 有个需求,需要访问内网网络共享文件夹中的文件,有域控限制。 一开始直接在本地映射一个网络驱动器,然后像本地磁盘一样访问共享文件夹里的文件,比如:Y:\ 。 然后直接在程序中访问共享文件夹中的文件,如下代码: DirectoryInfo directoryInfo = new Direct…...
ubuntu 安装 towhee
安装Towhee pip3 install towhee如果你想在 towhee 中安装模型 pip3 install towhee.models打开python终端 python3引入towhee 数据转换是 Towhee 的核心;管道只是在有向无环图中连接在一起的一系列转换。所有预构建的 Towhee 管道都有代表当前任务的名称。 fr…...

ERP软件对Oracle安全产品的支持
这里的ERP软件仅指SAP ECC和Oracle EBS。 先来看Oracle EBS: EBS的认证查询方式,和数据库认证是一样的。这个体验到时不错。 结果中和安全相关的有: Oracle Database VaultTransparent Data Encryption TDE被支持很容易理解,…...

Linux 基础-常用的命令和搭建 Java 部署环境
文章目录 目录相关查看目录中的内容查看目录当前的完整路径切换目录 文件相关创建文件查看文件内容写文件vim 基础 创建删除创建目录 移动和复制移动(剪切粘贴)复制(复制粘贴) 搭建 Java 部署环境1. 安装 jdk2. 安装 tomcat1). 我们在自己电脑上下好 tomcat2). 从官网下载的 .z…...
c语言总结(解题方法)
项目前期处理: 1.首先需要确定项目的背景知识,即主要的难点知识,如指针,数组,结构体,以检索自己是否对项目所需的背景知识足够了解。 2.确定问题实现方法,即题目本身的实现方法,在c语…...
Webpack的ts的配置详细教程
文章目录 前言ts是什么?基础配置LoaderSource MapsClient types使用第三方类库导入其他资源 后言 前言 hello world欢迎来到前端的新世界 😜当前文章系列专栏:webpack 🐱👓博主在前端领域还有很多知识和技术需要掌握…...

传智杯第五届题解
B.莲子的机械动力学 分析:这题有个小坑,如果是00 0,结果记得要输出0。 得到的教训是,避免前导0出现时,要注意答案为0的情况。否则有可能会没有输出 #include<assert.h> #include<cstdio> #include<…...
Android 通过demo调试节点权限问题
Android 通过demo调试节点权限问题 近来收到客户反馈提到在应用层无法控制节点,于是写了一个简单的demo来验证节点的IO权限,具体调试步骤就是写一个按钮点击事件,当点击按钮时将需要验证的节点写为1(节点默认为1则写为0ÿ…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...

龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...

【kafka】Golang实现分布式Masscan任务调度系统
要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...

边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
蓝桥杯 2024 15届国赛 A组 儿童节快乐
P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡,轻快的音乐在耳边持续回荡,小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下,六一来了。 今天是六一儿童节,小蓝老师为了让大家在节…...

CentOS下的分布式内存计算Spark环境部署
一、Spark 核心架构与应用场景 1.1 分布式计算引擎的核心优势 Spark 是基于内存的分布式计算框架,相比 MapReduce 具有以下核心优势: 内存计算:数据可常驻内存,迭代计算性能提升 10-100 倍(文档段落:3-79…...

如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...