探索设计模式的魅力:为什么你应该了解装饰器模式-代码优化与重构的秘诀

在这里插入图片描述


设计模式专栏:http://t.csdnimg.cn/nolNS


开篇

    在一个常常需要在不破坏封装的前提下扩展对象功能的编程世界,有一个模式悄无声息地成为了高级编程技术的隐形冠军。我们日复一日地享受着它带来的便利,却往往对其背后的复杂性视而不见。它是怎样织入我们代码的丝线的呢?这个编程世界里的‘变形金刚’,究竟隐藏着什么秘密?让我们一起揭开装饰器模式的神秘面纱。

一、背景

    在软件开发中,由于需求的变化非常频繁,因此需要有一种方式来在不改变原有代码的基础上增加新功能。装饰器模式正好满足了这一需求。
    变化是唯一不变的定律。因此,使用装饰器模式可以帮助开发人员在不断变化的需求中保持代码的灵活性和可维护性。

二、装饰器模式概述

2.1 简介

    装饰器模式是一种设计模式,用于在不修改原始类代码的情况下,动态地给对象添加新的功能。它通过创建一个包装对象来包裹原有对象,并在包装对象中添加额外的职责。这样,通过在运行时动态地创建和附加这些包装对象,可以动态地扩展对象的功能。

2.2 场景

1. GUI设计:
    在图形用户界面(GUI)开发中,装饰器模式可以用于自定义组件的行为。例如,假设你有一个基本的文本框组件,你可以使用装饰器模式为其添加额外的功能,比如校验输入、实时自动完成等。通过装饰器模式,你可以在不改变原始组件代码的情况下,动态地为组件添加功能。
2. 数据流处理:
    在数据处理流程中,装饰器模式可以用于对数据流进行过滤或转换。例如,假设你有一组数据流(比如通过传感器采集的数据),你可以使用装饰器模式为数据流添加解密、压缩或加密等功能。通过装饰器模式,你可以在不修改原始数据流代码的情况下,增加新的数据处理能力。
3. 日志记录:
    在日志记录系统中,装饰器模式可以用于为不同的日志记录类添加额外的功能。例如,你可能有一个基本的日志记录器,可以将日志信息写入文件。你可以使用装饰器模式为该日志记录器添加时间戳或格式化日志信息等功能,而无需修改原始的日志记录器代码。
5. 身份验证:
    在身份验证过程中,装饰器模式可以用于为已有的身份验证系统添加额外的验证功能。例如,假设你有一个基本的用户身份验证系统,你可以使用装饰器模式为该系统添加双因素身份验证、IP白名单验证等功能。

三、深入装饰器模式的结构

3.1 核心组件解析

1. Component:
    这是被装饰的对象的基本接口或抽象类,定义了对象必须实现的操作。装饰器和具体的被装饰对象都应当实现这个接口。
2. ConcreteComponent:
    这是Component接口的一个具体实现类。它定义了原始对象的基本功能,也即我们打算添加新功能的对象。
3. Decorator:
    这是一个抽象类,它实现(或继承)Component接口并包含一个Component类型的实例变量。这个类通常会有一个构造器,用于接收一个Component对象,并为其提供一个额外的功能层。Decorator本身通常是抽象的,因为它的目的是定义一个与ConcreteComponent兼容的接口,并为子类实现共通的任务——保持对Component实例的引用。
4. ConcreteDecorator:
    这是Decorator子类的具体实现。每个ConcreteDecorator通常实现了在Component上添加的额外功能。它会调用其内部的Component实例的方法,并在调用前后添加新的行为。

    这个模式的核心在于允许功能的层层叠加,这是通过持有Component实例的引用,并将对其的调用委托给这个实例来实现的,从而不会影响其他对象。实际上,对象可以通过一个或多个ConcreteDecorator来增加任意数量的责任。

3.2 模式原理阐述

    装饰器模式(Decorator Pattern)是一种设计模式,属于结构型模式的一种,它允许向一个现有的对象添加新的功能,而不改变其结构。这种模式创建了一个装饰类,用来包装原有的类,并且在不改变原类文件的前提下为原类动态地添加功能。
    装饰器模式的原理可以用以下几个要点来概括:
1. 继承关系的替代:
    装饰器模式提供了一种灵活的替代扩展系统的方法,而不是通过继承增加子类的方式来扩展功能,因为继承是静态的,并且应用于整个类,而装饰器模式可以在运行时动态添加特定对象的功能。
2. 接口一致性:
    装饰器模式使用组件(Component)接口对客户端透明,这意味着客户端针对接口编程,而不是针对具体类编程。装饰器类实现或继承自同一个接口,确保装饰后的对象仍然遵循原有接口的约定。
3. 动态包装:
    在装饰器模式中,装饰类包裹着组件对象,从而在保持组件接口的同时,为组件对象添加了额外的行为(装饰)。因为多态性的原因,客户端可以自由地使用装饰后的对象。
4. 多层装饰:
    装饰器模式允许多个装饰器按顺序装饰对象,从而实时增加新的行为或职责。装饰链中的每个装饰器可以向对象添加附加的状态或行为。
5. 增加灵活性:
    装饰器模式让用户可以选择需要的装饰器来组合功能,增强了系统的灵活性,同时代码维护和扩展也变得更加方便。

四、装饰器模式的超能力

    让我们通过一个简单的装饰器模式例子来阐释这个概念。假设我们在一个饮料店打工,需要为顾客提供几种不同类型的咖啡。起初,我们只提供简单的黑咖啡,但是顾客可以选择添加多种调料,如牛奶、糖、摩卡和奶泡。
    首先,我们建立一个饮料的抽象类,并让所有的咖啡和调料装饰器继承这个类:
// 抽象组件,定义饮料
public abstract class Beverage {String description = "Unknown Beverage";public String getDescription() {return description;}public abstract double cost();
}

    现在,我们添加我们的基础组件,黑咖啡:

// 具体组件,实现抽象组件
public class BlackCoffee extends Beverage {public BlackCoffee() {description = "Black Coffee";}public double cost() {return 1.00;}
}

    接下来,我们定义装饰器的抽象类,所有的调料装饰器都应该继承它:

// 装饰器抽象类,扩展自Beverage
public abstract class CondimentDecorator extends Beverage {public abstract String getDescription();
}

    然后我们为每种调料创建具体的装饰器实现类:

// 牛奶装饰器
public class Milk extends CondimentDecorator {Beverage beverage;public Milk(Beverage beverage) {this.beverage = beverage;}public String getDescription() {return beverage.getDescription() + ", Milk";}public double cost() {return beverage.cost() + 0.20;}
}// 糖装饰器
public class Sugar extends CondimentDecorator {Beverage beverage;public Sugar(Beverage beverage) {this.beverage = beverage;}public String getDescription() {return beverage.getDescription() + ", Sugar";}public double cost() {return beverage.cost() + 0.10;}
}// 摩卡装饰器
public class Mocha extends CondimentDecorator {Beverage beverage;public Mocha(Beverage beverage) {this.beverage = beverage;}public String getDescription() {return beverage.getDescription() + ", Mocha";}public double cost() {return beverage.cost() + 0.50;}
}// 奶泡装饰器
public class Whip extends CondimentDecorator {Beverage beverage;public Whip(Beverage beverage) {this.beverage = beverage;}public String getDescription() {return beverage.getDescription() + ", Whip";}public double cost() {return beverage.cost() + 0.30;}
}

    最后,我们可以这样组合它们来创建不同的咖啡:

public class CoffeeShop {public static void main(String args[]) {// Order a black coffee without any condimentsBeverage blackCoffee = new BlackCoffee();System.out.println(blackCoffee.getDescription() + " $" + blackCoffee.cost());// Add milk and sugar to the coffeeBeverage coffeeWithMilkAndSugar = new BlackCoffee();coffeeWithMilkAndSugar = new Milk(coffeeWithMilkAndSugar);coffeeWithMilkAndSugar = new Sugar(coffeeWithMilkAndSugar);System.out.println(coffeeWithMilkAndSugar.getDescription() + " $" + coffeeWithMilkAndSugar.cost());// Make a fancy coffee by adding Milk, Mocha, and Whip to the coffeeBeverage fancyCoffee = new BlackCoffee();fancyCoffee = new Milk(fancyCoffee);fancyCoffee = new Mocha(fancyCoffee);fancyCoffee = new Whip(fancyCoffee);System.out.println(fancyCoffee.getDescription() + " $" + fancyCoffee.cost());}
}

    在这个例子中,我们可以看到装饰器模式如何在不更改BlackCoffee类的情况下动态地为咖啡添加额外的调料。我们可以随意添加新的装饰器以实现新的功能,这展示了模式的灵活性。

五、装饰器模式的优缺点分析

5.1 优点

1. 增强扩展性:
    装饰器模式使得可以在不修改原始类代码的情况下通过添加装饰器在运行时扩展对象的行为。这使得遵循开闭原则(即软件实体应该对扩展开放,对修改关闭)成为可能,增加了类的灵活性和可扩展性。
2. 动态添加功能:
    与继承相比,装饰器模式能够动态地、非侵入式地添加或删除对象的责任。用户可以根据需要装饰对象,而不是在编译时决定其行为。
3. 功能组合:
    使用装饰器模式,多个装饰器可以组合起来使用,提供了一种非常灵活的方式来组合不同的功能。比如,在上面的咖啡例子中,你可以随意添加牛奶、糖、摩卡等,实现各种不同的咖啡组合。
4. 替代子类继承:
    由于使用子类继承来扩充功能可能会导致类爆炸现象(生成大量的子类),装饰器模式可以作为一种更优雅的解决方案,避免了类层次过多的缺点。
5. 保持类接口的纯净性:
    装饰器可以让我们把那些不适合放在类内部的功能,或者是那些会频繁更改的功能移出类外,这样可以保持原有的类结构清晰简单。
6. 细粒度的控制:
    装饰器模式提供了比继承更加细粒度的控制。每个装饰器可以实现更加精细的控制和管理。
7. 有利于解耦:
    装饰器模式有助于将类的核心职责和装饰功能进行分离,从而降低了系统的复杂度,并且有助于类的功能独立发展,减少了类之间的依赖关系。

5.2 缺点

1. 复杂性增加:
    使用装饰器模式可能会引入许多小类,因为每个装饰器通常对应一个小类。这有可能导致系统的结构变得复杂,特别是当装饰器过多或层次很深时。
2. 使用复杂性:
    如果使用过多的装饰器,那么客户端代码需要处理多个装饰层,这有可能使得对象的实例化过程变得复杂和难以理解。
3. 设计难度:
    正确并高效地使用装饰器模式需要对系统设计有深入理解。开发人员需要能够明确区分何时应该使用装饰器,何时应该使用简单的继承,以及何时选择其它设计模式。
4. 调试难度:
    对于装饰过的对象,调试可能更困难,因为装饰器会在调用栈中引入额外的层次。这意味着错误可能会在多个层次之间传播,而定位问题的根源可能更加耗时。
5. 接口不匹配:
    如果基础组件的接口随着时间发生变化,所有依赖于那个接口的装饰器都需要相应地进行更新,这可能会导致维护难度增加。
6. 过度使用:
    在不恰当的情况下强迫使用装饰器模式会使得设计过于复杂,简单问题可能仅仅需要更直接的方法。
7. 性能考虑:
    虽然大多数情况下这不是问题,但是在复杂的装饰器链中,每次调用都会通过所有的装饰器层,这可能会对性能产生不利影响。尤其是对于一些对性能要求高的系统,可能需要考虑额外的优化。

六、装饰器模式在现实世界中的应用

七、从装饰器模式看设计原则

7.1 开闭原则

    一个软件实体应当对扩展开放,对修改关闭。这一原则最早由BertrandMeyer [MEYER88]提出,英文原文是:Software entities should be open for extension, but closed for modification.

    这个原则说的是,在设计一个模块的时候,应当使这个模块可以在不被修改的前提下被扩展。换言之,应当可以在不必修改源代码的情况下增强这个模块的行为。

7.2 合成复用原则

    合成/聚合复用原则 (Composite/Aggregate Reuse Principle,或 CARP)经常又叫做合成复用原则(Composite Reuse Principle或CRP)。合成/聚合复归原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象迪过向这些对象的委派达到复用已有功能的日的。

    这个设计原则有只 - 个更简短的表述:要尽量使用合成/聚合,尽量不要使用继承。

7.3 装饰器模式中的开闭原则

实践

1. 不修改现有代码的情况下扩展功能:
    装饰器模式允许在不改变原始类的源代码和不增加新的子类的情况下,动态的扩展对象的功能。通过将每个要添加的功能封装在一个装饰器类中,可以组合使用多个装饰器来增强对象的功能。
2. 运行时选择功能:
    与静态继承相比,装饰器模式通过组合的方式在运行时动态地为对象添加额外的职责,这样就可以根据需要来增加或修改对象的行为,实现开闭原则中提到的对扩展开放。
3. 维护对象接口不变:
    装饰器具备与继承的对象相同的接口,这意味着从客户端的角度看,装饰的对象与原始对象遵循相同的接口(即它们可以是透明的),这保证了原始对象的接口不受影响,同时也可以提供新的功能。
4. 松耦合设计:
    装饰器模式促进了良好的设计原则,比如单一职责原则和开闭原则,装饰器类通常专注于一个具体的功能,并且可以在不影响其他装饰器或对象的情况下单独更改。

7.3 装饰器模式中的合成复用原则

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

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

相关文章

Ingress

文章目录 环境准备什么是 Ingress认识 Ingress 资源Ingress 控制器(controller)Ingress 规则pathType 路径类型多重匹配Ingress 类TLS生成证书创建密钥 环境准备 下面的 yaml 文件内容,是使用 sts 创建两个 web 服务,并配置对应的 servcie。web 服务的首…

【MybatisPlus篇】查询条件设置(范围匹配 | 模糊匹配 | 空判定 | 包含性判定 | 分组 | 排序)

文章目录 🎄环境准备⭐导入依赖⭐写入User类⭐配置启动类⭐创建UserDao 的 MyBatis Mapper 接口,用于定义数据库访问操作⭐创建配置文件🛸创建测试类MpATest.java 🍔范围查询⭐eq⭐between⭐gt 🍔模糊匹配⭐like &…

基于flask的个人博客项目从0到1

项目展示(持续完善中…) 首页 文章时间线页面 笔记页面 留言页面 关于页面 后台页面-文章管理 后台页面-笔记页面 后台页面-分类 后台管理-新增标签 后台管理-标签页面 后台管理-新增标签 后台管理-关于页面 2.项目详述 该博客开源地址点击跳转,该项目已部署上…

flowable 设置自定义属性教程

概述 由于工作需要给flowable工作流设计器添加自定义属性,以满足功能实现。所以这篇文章介绍下用flowable 开源的的flowable-ui 前端添加自定义属性,后端解析属性值的例子。 技术栈 序号技术点名称版本1Flowable6.8.0 使用的是flowable6.8.0 版的代码…

如何在Windows部署GoLand并通过SSH远程连接Linux服务器

文章目录 1. 安装配置GoLand2. 服务器开启SSH服务3. GoLand本地服务器远程连接测试4. 安装cpolar内网穿透远程访问服务器端4.1 服务器端安装cpolar4.2 创建远程连接公网地址 5. 使用固定TCP地址远程开发 本文主要介绍使用GoLand通过SSH远程连接服务器,并结合cpolar内…

二叉树(1)

1 树概念及结构 1.1树的概念 树是一种非线性的数据结构,它是由n(n>0)个有限结点组成一个具有层次关系的集合。 把它叫做树是因为它看起来像一棵倒挂的树,也就是说它是根朝上,而叶朝下的。 有一个特殊的结点&a…

c++设计模式之适配器模式

作用 适配器模式的作用是可以让不兼容的接口在一起工作 案例 假如现在有一台电脑和一台投影仪,现在需要把电脑和投影仪进行连接 在此基础上我们再假设,电脑只能连接VGA接口,而投影仪的种类繁多,有VGA接口、HAMI接口等多种种类…

初探分布式链路追踪

本篇文章,主要介绍应用如何正确使用日志系统,帮助用户从依赖、输出、清理、问题排查、报警等各方面全面掌握。 可观测性 可观察性不单是一套理论框架,而且并不强制具体的技术规格。其核心在于鼓励团队内化可观察性的理念,并确保由…

低版本MATLAB打开高版本Simulink文件的方法

打开simulink,依次点击“建模”、“环境”、“simulink预设项”,如图所示: 然后在弹出的窗口中,点击“模型文件”,并取消勾选“不要加载用更新版本的simulink创建的模型”,接着点击“应用”即可。如图所示&…

跟着cherno手搓游戏引擎【19】抽象纹理

引入: 导入stb_image: GitHub - nothings/stb: stb single-file public domain libraries for C/C 下载复制stb_image.h的内容(8000多行),然后粘到如图位置 stb_image.cpp: #include"ytpch.h" #define STB_IMAGE_IM…

Revit中使用依赖注入

依赖注入的技术已经很成熟,本文主要是说明一下Revit中的适用版本与介绍相关的开源项目。 版本问题 版本 目前的依赖注入包无法支持Revit 2020 以下的版本,原因是因为包中的依赖项与Revit本身的依赖项不一致导致的,所以说如果使用Revit DI…

题目:有1,2,3,4共四个数字,能组成多少个不相同而且无重复数字的三位数有多少个,都是多少?lua

这是作者的思路, 创建三个表, 第一个数是从四个数遍历, 第二个是数剔除第一个数进行遍历 第三个是剔除第一第二个数遍历 脚本如下 local a{1,2, 3, 4} local b{} local c{} local d{} local function copy(tbl) local ctbl{} for k,v in…

机器学习复习(4)——CNN算法

目录 数据增强方法 CNN图像分类数据集构建 导入数据集 定义trainer 超参数设置 数据增强 构建CNN网络 开始训练 模型测试 数据增强方法 # 一般情况下,我们不会在验证集和测试集上做数据扩增 # 我们只需要将图片裁剪成同样的大小并装换成Tensor就行 test_t…

nginx初学者指南

一、启动、停止和重新加载配置 前提:先要启动nginx 在Windows上启动nginx的步骤如下: 1. 下载并安装nginx。可以从nginx官网下载适合自己操作系统的版本,一般是zip压缩包,解压到指定目录中。 2. 进入nginx的安装目录&#xff…

LeetCode:138. 随机链表的复制之如何有效copy

自己复制的话,很容易写出来一个时间复杂度O(n ^ 2) 空O(n)的做法 我们可以参考基因的复制, 目录 题目: 实现思路(基因复制式的copy): 官方快慢指针解法&…

基于Python的招聘网站爬虫及可视化的设计与实现

摘要:现在,随着互联网网络的飞速发展,人们获取信息的最重要来源也由报纸、电视转变为了互联网。互联网的广泛应用使网络的数据量呈指数增长,让人们得到了更新、更完整的海量信息的同时,也使得人们在提取自己最想要的信…

山东淄博刑侦大队利用无人机抓获盗窃团伙

山东淄博刑侦大队利用无人机抓获盗窃团伙 近期,山东淄博临淄区发生多起盗窃案件。通过视频追踪和调查访问,推断临淄区某村可能为嫌疑人藏匿地点。刑侦大队无人机应急小组迅速到达现场,经无人机高空侦查,发现并锁定了嫌疑人的藏匿…

【开源】SpringBoot框架开发城市桥梁道路管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 查询城市桥梁4.2 新增城市桥梁4.3 编辑城市桥梁4.4 删除城市桥梁4.5 查询单个城市桥梁 五、免责说明 一、摘要 1.1 项目介绍 基于VueSpringBootMySQL的城市桥梁道路管理系统,支持…

灵伴科技(Rokid)借助 Knative 实现 AI 应用云原生 Serverless 化

作者:朱炜栋、元毅、子白 公司介绍 Rokid 创立于 2014 年,是一家专注于人机交互技术的产品平台公司,2018 年即被评为国家高新技术企业。Rokid 作为行业的探索者、领跑者,目前致力于 AR 眼镜等软硬件产品的研发及以 YodaOS 操作系…

B3626 跳跃机器人——洛谷(疑问)

题目描述 地上有一排格子,共 �n 个位置。机器猫站在第一个格子上,需要取第 �n 个格子里的东西。 机器猫当然不愿意自己跑过去,所以机器猫从口袋里掏出了一个机器人!这个机器人的行动遵循下面的规则&#…