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

在这里插入图片描述


设计模式专栏: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 condiments
        Beverage blackCoffee = new BlackCoffee();
        System.out.println(blackCoffee.getDescription() 
                           + " $" + blackCoffee.cost());
        
        // Add milk and sugar to the coffee
        Beverage 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 coffee
        Beverage 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 装饰器模式中的合成复用原则

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

幻兽帕鲁服务器多少钱?价格PK阿里云腾讯云华为云

2024年幻兽帕鲁服务器价格表更新,阿里云、腾讯云和华为云Palworld服务器报价大全,4核16G幻兽帕鲁专用服务器阿里云26元、腾讯云32元、华为云26元,阿腾云atengyun.com分享幻兽帕鲁服务器优惠价格表,多配置报价: 幻兽帕鲁…

【C++】C++入门— 类与对象初步介绍

C入门 1 认识面向对象2 类的引入3 类的定义类的定义方式 4 类的访问限定符及封装访问限定符封装 Thanks♪(・ω・)ノ谢谢阅读!下一篇文章见!!! 1 认识面向对象 C语言是面向过程的,关注…

位运算之妙用:识别独特数字(寻找单身狗)

目录 找单身狗1 图解: 代码如下: 找单身狗2 图解: 代码如下: 寻找单身狗1 从数组中 的1 2 3 4 5 1 2 3 4 中找出没有另一个相同的数与其匹配的数 这个问题的原理是利用异或运算的性质。异或运算(XOR&#xff09…

java学习03 判断和循环

一 流程控制语句 1.流程控制语句分类 顺序结构 判断和选择结构(if, switch) 循环结构(for, while, do…while) 2. 顺序结构 顺序结构是程序中最简单最基本的流程控制,没有特定的语法结构,按照代码的先后顺序,依次执行,程序中…

Blender使用Rigify和Game Rig Tool基础

做动画需要的几个简要步骤: 1.建模 2.绑定骨骼 3.绘制权重 4.动画 有一个免费的插件可以处理好给引擎用:Game Rig Tool 3.6和4.0版本的 百度网盘 提取码:vju8 1.Rigify是干嘛用的? 》 绑定骨骼 2.Game Rig Tool干嘛用的&#xf…

Revit中使用依赖注入

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

2V2无人机红蓝对抗仿真

两架红方和蓝方无人机分别从不同位置起飞,蓝方无人机跟踪及击毁红方无人机 2020a可正常运行 2V2无人机红蓝对抗仿真资源-CSDN文库

探索Gin框架:Golang使用Gin完成文件上传

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站https://www.captainbed.cn/kitie。 前言 在之前的文章中,我们讲解了Gin框架的快速入门使用,今天我们来聊聊如何使用…

单细胞scATAC-seq测序基础知识笔记

单细胞scATAC-seq测序基础知识笔记 单细胞ATAC测序前言scATAC-seq数据怎么得出的? 该笔记来源于 Costa Lab - Bioinformatics Course 另一篇关于scRNA-seq的请移步 单细胞ATAC测序前言 因为我的最终目的是scATAC-seq的数据,所以这部分只是分享下我刚学…

c++类继承

一、继承的规则 (1)基类成员在派生类中的访问权限不得高于继承方式中指定的权限。例如,当继承方式为protected时,那么基类成员在派生类中的访问权限最高也为protected,高于protected会降级为protected,但低…

阿里云部署的幻兽帕鲁服务器,为什么没有一键更新游戏服务端、导入存档、可视化的游戏配置

如果有的朋友发现自己用阿里云部署的幻兽帕鲁服务器,找不到一键更新游戏服务端、一键导入存档、以及可视化的游戏配置。其实答案很简单,因为你的“阿里云计算巢”版本需要更新。你只需要更新到最新版,就可以拥有这些便捷的功能了。 具体的操…

【备战蓝桥杯】——循环结构终篇

🌈个人主页: Aileen_0v0 🔥热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​💫个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-yl4Tqejg4LkjZLAM {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

一文辨析清楚LORA、Prompt Tuning、P-Tuning、Adapter 、Prefix等大模型微调方法

本文探讨了大模型微调的核心概念和方法,详细介绍了如LoRA、Adapter Tuning、Prefix Tuning等多种微调策略。每种方法的原理、优势及适用场景都有详尽阐述,大家可以根据不同的应用需求和计算资源,选择到最合适自己的微调途径。 希望本文能对想…

Linux下grep命令详解

grep #文件内容过滤显示 #在指定的普通文件中查找并显示含有指定字符串的行,也可与管道符一起使用格式: grep-参数 查找条件 文件名 参数: 示例: [rootnode1 ~]# grep -n "root" /etc/passwd # -n&a…

LangChain结合通义千问的自建知识库

LangChain结合通义千问的自建知识库 在使用了通义千问API了之后,下一步就是构建知识库文档,使用了比较有名的LangChian,最后成果将自己的txt生成了知识向量库,最后我还把自己的论文生成了一个知识向量库,然后问他我的…

Java 基于 SpringBoot+Vue 的前后端分离的火车订票管理系统

博主介绍:✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专栏推荐订阅👇…

Cambalache in Ubuntu

文章目录 前言apt install flatpak这很ok后记 前言 gtkmm4相比gtkmm3有很多改革, 代码也干净了许多, 但在windows上开发 有ui设计器那自然方便很多, 但glade又不支持gtkmm4, windows上装Cambalache很是困难. 各种问题都找不到答案.于是 我用VMware虚拟机Ubuntu20.xx安装Cambal…

C++集群聊天服务器 网络模块+业务模块+CMake构建项目 笔记 (上)

跟着施磊老师做C项目&#xff0c;施磊老师_腾讯课堂 (qq.com) 一、网络模块ChatServer chatserver.hpp #ifndef CHATSERVER_H #define CHATSERVER_H#include <muduo/net/TcpServer.h> #include <muduo/net/EventLoop.h> using namespace muduo; using namespace …

5分钟快速掌握 XML (Extensible Markup Language)

背景 在Java开发的过程中&#xff0c;我们经常需要和配置文件打交道&#xff0c;其中接触最多的就是XML。从最初学习 JavaWeb 时在 Tomcat 中配置servlet&#xff0c;到后来接触Spring框架并在XML中编写各种配置&#xff0c;XML一直是不可或缺的一部分。然而&#xff0c;XML的…

在Vue中如何构建复杂表单?

概述 很有可能&#xff0c;在我们的软件工程旅程中&#xff0c;我们至少要构建一次复杂的表单。本文将介绍如何创建一个复杂的表单&#xff0c;该表单可以使用一些Vue特性(如v-for和v-model)逐步增强。它还提供了一些基本的Vue核心功能的复习&#xff0c;这些功能将在您日常使…