设计模式之装饰模式(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产生关系,就变成了右边的图。现在看这张图和我开篇放的那张装饰模式的图是不是基本一致了,只是缺少了聚合关系。
关于聚合关系,在业务实现过程中并没有体现,这点以后有机会再研究。

在这里插入图片描述

总结

    装饰模式算比较难的设计模式了,进行了多次学习,每次学习都有不一样的收获,而且越来越有意思,希望今天的文章也能带给你同样的感受。如果你也有有意思的想法和不同意见,欢迎指教,最后感谢阅读。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/206390.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Maxwell安装部署消费到kafka集群

1.上传安装包到linux系统上面 2.解压安装包到安装目录下,并且重命名 [rootVM-4-10-centos package]# tar -zxvf maxwell-1.29.2.tar.gz -C /opt/software/3.配置mysql 增加以下配置 #数据库id server-id 1 #启动binlog,该参数的值会作为binlog的文件…

phpoffice在tp框架中如何实现导入导出功能

安装 phpoffice/phpspreadsheet 库 composer require phpoffice/phpspreadsheet 导入功能 创建一个用于上传文件的视图&#xff0c;可以使用元素来实现文件上传。 <!-- application/view/your/import.html --><form action"{:url(your/import)}" method&q…

ros2智能小车中STM32地盘需要用到PWM的模块

我做的地盘比较简单&#xff0c;使用了一下模块&#xff1a; 4个直流减速电机&#xff0c;&#xff08;每个模块用到了一个PWM&#xff09;---这会通过L298N的ENA,ENB来实现控制 光电对射测速模块&#xff08;不用PWM) 超声波测距模块&#xff08;不用PWM&#xff0c;只需要…

AcWing 3555:二叉树(北京大学考研机试题)→公共父结点

【题目来源】https://www.acwing.com/problem/content/description/3435/【题目描述】 如下图所示&#xff0c;由正整数 1, 2, 3, … 组成了一棵无限大的&#xff08;满&#xff09;二叉树。 1/ \2 3/ \ / \4 5 6 7 /\ /\ /\ /\ ... ... 从任意一个结点到根结点&…

记一次处理大数据而导致的内存溢出问题

问题 订单服务通过MQ进行订单同步时&#xff0c;刚启动可以正常消费&#xff0c;但是跑一会就会卡住&#xff0c;每次都是第8个kafka分区不行再进行消费&#xff0c;其他分区消费的很慢。 现象 首先&#xff0c;CPU超高&#xff0c;达到百分之300多&#xff1b;其次&#xf…

如何使用Qchan搭建更好保护个人隐私的本地图床并在公网可访问

文章目录 前言1. Qchan网站搭建1.1 Qchan下载和安装1.2 Qchan网页测试1.3 cpolar的安装和注册 2. 本地网页发布2.1 Cpolar云端设置2.2 Cpolar本地设置 3. 公网访问测试总结 前言 图床作为云存储的一项重要应用场景&#xff0c;在大量开发人员的努力下&#xff0c;已经开发出大…

Redis的持久化(新)

Redis中数据都保存在内存&#xff0c;但是内存中的数据变换很快&#xff0c;也很容易丢失&#xff0c;比如连接断开、宕机停机等等。而Redis提供的数据持久化机制有RDB(Redis DataBase)和AOF(Append Only File)。 1.RDB RDB是指在指定的时间间隔内将内存中的数据集快照写入到磁…

CodeTON Round 7 (Div. 1 + Div. 2, Rated, Prizes!)

B. AB Flipping 老规矩&#xff0c;自己要模拟一遍样例&#xff0c;发现样例还不够&#xff0c;就自己构造样例&#xff0c;这样做着做着就会有思路。 分析&#xff1a;假如现在有这样一个字符串 BBBAABABBAAA。会发现前三个和后三个一定是不会被操作的&#xff0c;因为不会满…

git-2

1.分离头指针情况下的注意事项 分离头指针指的是变更没有基于某个branch去做&#xff0c;所以当进行分支切换的时候&#xff0c;在分离头指针上产生的commit&#xff0c;很可能会被git当作垃圾清理掉&#xff0c;如果你认为是重要的内容&#xff0c;切记需要绑定分支 2.进一步…

人工智能基础_机器学习050_对比sigmoid函数和softmax函数的区别_两种分类器算法的区别---人工智能工作笔记0090

可以看到最上面是softmax的函数对吧,但是如果当k = 2 那么这个时候softmax的函数就可以退化为sigmoid函数,也就是 逻辑斯蒂回归了对吧 我们来看一下推导过程,可以看到上面是softmax的函数 可以看到k=2 表示,只有两个类别对吧,两个类别的分类不就是sigmoid函数嘛对吧,所以说 …

关闭vscode打开的本地服务器端口

vscode开了本地的一个端口“8443”当本地服务器端口&#xff0c;然后随手把VScode一关&#xff0c;后来继续做发现8443端口已经被占用了。   原来&#xff0c;即便关闭了编译器VScode&#xff0c;服务器依然是被node.exe运行着的。那这个端口怎么才能关掉呢&#xff1f;   …

vue3中toRef创建一个ref对象

为源响应式对象上的某个属性创建一个 ref对象, 二者内部操作的是同一个数据值, 更新时二者是同步的 区别ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响 应用: 当要将 某个prop 的 ref 传递给复合函数时&#xff0c;toRef 很有用 父组件代码: <template><…

第十五届蓝桥杯(Web 应用开发)模拟赛 2 期-大学组(详细分析解答)

目录 1.相不相等 1.1 题目要求 1.2 题目分析 1.3 源代码 2.三行情书 2.1 题目要求 2.2 题目分析 2.3 源代码 3.电影院在线订票 3.1 题目要求 3.2 题目分析 3.3 源代码 4.老虎坤&#xff08;不然违规发不出来&#xff09; 4.1 题目要求 4.2 题目分析 4.3 源代码 …

Go语言网络爬虫工程经验分享:pholcus库演示抓取头条新闻的实例

网络爬虫是一种自动从互联网上获取数据的程序&#xff0c;它可以用于各种目的&#xff0c;如数据分析、信息检索、竞争情报等。网络爬虫的实现方式有很多&#xff0c;不同的编程语言和框架都有各自的优势和特点。在本文中&#xff0c;我将介绍一种使用Go语言和pholcus库的网络爬…

excel单元格内换行按什么快捷键

如果我们使用excel软件的时候&#xff0c;因为一些日常的操作太过繁琐想要简化自己的操作步骤的话&#xff0c;其实是有很多快捷方式在其中的。那么对excel单元格内换行按什么快捷键这个问题&#xff0c;据小编所知我们可以在表格中使用Alt Enter来进行换行。详细内容就来看下…

奇异值分解SVD(Singular Value Decomposition)

原理 奇异值分解是让机器从大量数据集中提取出关键信息的重要手段。&#xff08;矩阵中的泰勒公式&#xff0c;哈哈&#xff09; 思考&#xff1a;如果就矩阵的本质是线性变换而言的话&#xff0c;那A矩阵分解后是不是相当与先进行旋转→放缩→旋转呢&#xff1f; 求解奇异值的…

E云管家微信群聊机器人开发

请求URL&#xff1a; http://域名地址/modifyGroupRemark 请求方式&#xff1a; POST 请求头Headers&#xff1a; Content-Type&#xff1a;application/jsonAuthorization&#xff1a;login接口返回 参数&#xff1a; 参数名必选类型说明wId是String登录实例标识chatRo…

linux获得帮助_如何查看命令的用法、作用

Linux获得帮助 多层次的帮助&#xff1a; whatis command --help man and info /usr/share/doc/ Red Hat documentation 、Ubuntu documentation 软件项目网站 其它网站 搜索 whatis 使用数据库来显示命令的简短描述。 [rootlocalhost ~]# whatis rm rm (1) …

腾讯云双11活动最后一天,错过再等一年!

腾讯云双11活动已经进入尾声&#xff0c;距离活动结束仅剩最后一天&#xff0c;记得抓住这次上云好时机&#xff0c;错过这次&#xff0c;就要等到下一年才能享受到这样的优惠力度了&#xff01; 活动地址&#xff1a; 点此直达腾讯云双11活动主会场 活动详情&#xff1a; 1…

Linux中tar命令的几个高级用法

在Linux世界中&#xff0c;Tar命令是一把解密归档世界的魔法工具。无论是打包、压缩还是解压&#xff0c;Tar命令都能胜任。本文将生动地介绍Tar命令的基本用法&#xff0c;并深入探讨五个常用选项&#xff0c;帮助读者在Linux系统中灵活运用这个强大的工具。 一、命令概述 Ta…