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

在这里插入图片描述


设计模式专栏: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.mzph.cn/news/665393.shtml

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

相关文章

项目安全-----加密算法实现

目录 对称加密算法 AES (ECB模式) AES(CBC 模式)。 非对称加密 对称加密算法 对称加密算法,是使用相同的密钥进行加密和解密。使用对称加密算法来加密双方的通信的话,双方需要先约定一个密钥,加密方才能加密&#…

C++ 动态规划 线性DP 最长共同子序列

给定两个长度分别为 N 和 M 的字符串 A 和 B ,求既是 A 的子序列又是 B 的子序列的字符串长度最长是多少。 输入格式 第一行包含两个整数 N 和 M 。 第二行包含一个长度为 N 的字符串,表示字符串 A 。 第三行包含一个长度为 M 的字符串,表…

Servlet+Ajax实现对数据的列表展示(极简入门)

目录 1.准备工作 1.数据库源(这里以Mysql为例) 2.映射实体类 3.模拟三层架构(Dao、Service、Controller) Dao接口 Dao实现 Service实现(这里省略Service接口) Controller层(或叫Servlet层…

【blender插件】(1)快速开始

特性 blender的python API有如下特性: 编辑用户界面可以编辑的任意数据(场景,网格,粒子等)。修改用户首选项、键映射和主题。运行自己的配置运行工具。创建用户界面元素,如菜单、标题和面板。创建新的工具。场景交互式工具。创建与Blender集成的新渲染引擎。修改模型的数据…

LeetCode.1686. 石子游戏 VI

题目 题目链接 分析 本题采取贪心的策略 我们先假设只有两个石头a,b, 对于 Alice 价值分别为 a1,a2, 对于 Bob 价值而言价值分别是 b1,b2 第一种方案是 Alice取第一个,Bob 取第二个,Alice与Bob的价值差是 c1 a1 - b1&#xf…

【Crypto | CTF】BUUCTF rsarsa1

天命:第二题RSA解密啦,这题比较正宗 先来看看RSA算法 这道题给出了 p,q,E,就是给了两个质数和公钥 有这三个东西,那就可以得出私钥了 最后把私钥和质数放进去解密即可得到解密后的明文 from gmpy2 impor…

会计的记账凭证

目录 一. 记账凭证的填制与审核1.1 收付款凭证1.2 转账凭证1.3 单式记账凭证 二. 记账凭证的编号 \quad 一. 记账凭证的填制与审核 \quad \quad 1.1 收付款凭证 \quad 注意︰ 凡是涉及货币资金之间收付款的业务如将库存现金存入银行或从银行提取现金等类经济业务。在实际工作中…

程序员的悲哀:知名Python库requests作者失业了

在当今这个快速发展的科技时代,程序员作为创新的驱动力,一直被视为时代的宠儿。然而,即使在这样一个充满机会的领域,也有着不为人知的辛酸。近日,一个令人震惊的消息传遍了编程社区:知名Python库requests的…

css1基础选择器

大纲 一.标签选择器 比较简单,前面直接写目标标签 二.类选择器 应用 例子 三.多类名选择器(调用时中间用空格隔开) 四.id选择器 应用 五.通配符选择器 应用 六.总结

gRPC使用详解

起源特点主要优缺点应用场景组成部分使用方法SpringBoot集成gRPCVert.x集成gRPCNacos集成gRPC监控gRPC调用过程Java使用示例 起源 gRPC的起源可以追溯到2015年,当时谷歌发布了一款开源RPC框架,名为gRPC。gRPC的设计初衷是为了提供一种标准化、可通用和跨…

【成品论文30页】2024美赛C题完整1-4问完整解答+答疑服务+完整数据集代码

2024美赛C题 也包括心理和环境因素。理解这些因素对于运动员、教练和观众来说都至关重要,因为它们直接影响着比赛的结果和整体体验。 技术水平和体能因紊 网球比赛中得分的波动首先受到运动员的技术水平和体能因素的影响。技术水平高的运动员往往能够以更加精准和有…

UnityShader(十四)纹理

目录 前言: 单张纹理实现效果: 效果: 前言: 纹理最初的目的是用一张图片来控制模型的外观。使用纹理映射技术我们可以把一张图“贴”在模型表面,逐纹素(文素的名字是为了和像素进行区分)控制…

CentOS 7中搭建FTP文件共享服务器的完整步骤

CentOS 7中搭建FTP文件共享服务器的完整步骤 要求:设置不允许匿名用户登录,只允许本地用户登录,且将活动范围限制在其家目录。 系统环境: 服务器:172.20.26.167-CentOS7.6 客户端:172.20.26.24-CentOS7…

泰克示波器(TBS2000系列)触发功能使用讲解——边沿触发

# Trigger区域 触发区域用于对触发功能进行配置。示波器的触发功能用于采集(Acquire)那些在瞬间出现的信号,便于我们分析观察,此时可以当做逻辑分析仪使用。触发区域按钮包括:menu、Level\Force Trig三个。 目录 1.1 …

一篇文章了解系统眼中的键盘--以一个简单的系统分析从按键的输入到字符的显示

键盘输入 实现使用的设备 intel架构32位CPU, 思路为嵌入式系统工程师,使用的操作系统是《30天自制操作系统》里面的系统进行讲解 硬件实现 按键 使用单片机等的引脚可以获取电平状态从而获得按键的状态(单片机是一种集成到一块硅片上构成的一个小而完善的微型计算机系统, 用…

分享一个WPF项目

最近在学习WPF开发方式,找到一些项目进行拆解学习;本位主要分享一个WPF项目,叫做WPFDevelopers,在git上大约有1.3K星,话不多说,先看看效果: 这个项目开发可以编译启动后直接查看样例、Xaml、Cha…

《计算机网络简易速速上手小册》第9章:物联网(IoT)与网络技术(2024 最新版)

文章目录 9.1 IoT 架构与通信协议 - 打造智能世界的秘诀9.1.1 基础知识9.1.2 重点案例:使用 Python 和 MQTT 实现智能家居照明系统准备工作Python 脚本示例发布者(灯光控制)订阅者(灯光状态接收): 9.1.3 拓…

快速Diff算法-Vue3

快速Diff算法 快速 Diff 算法在实测中性能最优。它借鉴了文本 Diff 算法中的预处理思路,先处理新旧两组子节点中相同的前置节点和相同的后置节点。当前置节点和后置节点全部处理完毕后,如果无法简单地通过挂载新节点或者卸载已经不存在的节点来完成更新…

AD24-Class、飞线、PCB Nets的管理及添加、层的管理

一、Class 1、Class介绍 2、Class添加与显示 ①添加 ②显示通过Panels-PCB,即可将创建的类显示再左上方窗口 3、Class的编辑管理 ①概述 ②颜色更改 二、飞线 1、概述 2、 飞线的打开、关闭 打开:Alt左上角滑动 N:可以针对性的显示和隐…

深度学习环境配置:Anaconda 安装和 pip 源

conda是一种通用包管理系统,与pip的使用类似,环境管理则允许用户方便地安装不同版本的python并可以快速切换。 Anaconda则是一个打包的集合,里面预装好了conda、某个版本的python、众多packages、科学计算工具等等,就是把很多常用…