装饰模式(Decorator Pattern)

定义

装饰模式(Decorator Pattern)是一种结构型设计模式,它允许通过将对象包装在装饰器类的实例中来动态地添加新的行为和责任。这种模式可以在不修改现有代码的情况下,灵活地扩展对象的功能。

示例

考虑一个咖啡店的场景,有不同种类的咖啡,你可以选择添加不同的配料,比如牛奶、糖和巧克力。使用装饰模式可以动态地为咖啡添加不同的配料,而不需要修改咖啡类的代码。

类结构

  1. Component(组件): 定义了一个抽象接口,用于具体组件和装饰器共享。
  2. ConcreteComponent(具体组件): 实现了Component接口的具体类,是被装饰的对象。
  3. Decorator(装饰器): 也实现了Component接口,并持有一个Component对象的引用,这是装饰的核心。
  4. ConcreteDecorator(具体装饰器): 扩展了Decorator类,负责具体的装饰操作。

代码实现

#include <iostream>
#include <string>

// Step 1: Component(组件)
class Coffee {
public:
    virtual std::string getDescription() const = 0;
    virtual double cost() const = 0;
};

// Step 2: ConcreteComponent(具体组件)
class SimpleCoffee : public Coffee {
public:
    std::string getDescription() const override {
        return "简单咖啡";
    }

    double cost() const override {
        return 5.0;
    }
};

// Step 3: Decorator(装饰器)
class CoffeeDecorator : public Coffee {
protected:
    Coffee* coffee;

public:
    CoffeeDecorator(Coffee* c) : coffee(c) {}

    std::string getDescription() const override {
        return coffee->getDescription();
    }

    double cost() const override {
        return coffee->cost();
    }
};

// Step 4: ConcreteDecorator(具体装饰器)
class MilkDecorator : public CoffeeDecorator {
public:
    MilkDecorator(Coffee* c) : CoffeeDecorator(c) {}

    std::string getDescription() const override {
        return coffee->getDescription() + ",加牛奶";
    }

    double cost() const override {
        return coffee->cost() + 2.0;
    }
};

class SugarDecorator : public CoffeeDecorator {
public:
    SugarDecorator(Coffee* c) : CoffeeDecorator(c) {}

    std::string getDescription() const override {
        return coffee->getDescription() + ",加糖";
    }

    double cost() const override {
        return coffee->cost() + 1.0;
    }
};

int main() {
    // 创建一个简单咖啡
    Coffee* myCoffee = new SimpleCoffee();
    std::cout << "描述:" << myCoffee->getDescription() << ",价格:" << myCoffee->cost() << "元" << std::endl;

    // 使用装饰器动态添加牛奶
    myCoffee = new MilkDecorator(myCoffee);
    std::cout << "描述:" << myCoffee->getDescription() << ",价格:" << myCoffee->cost() << "元" << std::endl;

    // 使用装饰器再添加糖
    myCoffee = new SugarDecorator(myCoffee);
    std::cout << "描述:" << myCoffee->getDescription() << ",价格:" << myCoffee->cost() << "元" << std::endl;

    // 释放内存
    delete myCoffee;

    return 0;
}

结构图示

在这个示例中,Coffee是组件,SimpleCoffee是具体组件,CoffeeDecorator是装饰器,MilkDecoratorSugarDecorator是具体装饰器。通过不同的组合,我们可以动态地扩展咖啡的描述和价格,而无需修改原始的咖啡类。这就是装饰模式

适用场景

动态地添加或修改对象的功能

当需要动态地为一个对象添加额外的功能,而且希望这些功能可以灵活组合时,装饰模式是一个很好的选择。这样可以避免使用大量子类来实现所有可能的组合,而是使用装饰器来动态地添加这些功能。


避免使用继承导致的类爆炸

经常会发现在类的层次结构中添加新功能导致的子类爆炸问题。装饰模式通过将功能分离到单独的装饰器类中,避免了这种情况的发生。


保持类的简单性和单一责任原则

使用装饰模式可以将一些复杂的功能分离到单独的装饰器类中,使得原始类保持简单和具有单一职责。

在运行时动态地添加或删除功能

装饰模式允许在运行时动态地添加或删除对象的功能,这对于某些情况下的配置和扩展非常有用。

经典使用方案


Java I/O库中的输入输出流

 Java中的输入输出流就是一个典型的装饰器模式的例子。基本的InputStream或OutputStream可以通过添加额外的功能,比如缓冲、加密或压缩等,而无需修改它们的代码。


GUI界面组件

 在GUI编程中,经常需要动态地添加新的功能或外观到用户界面组件上。比如,一个简单的文本框可以通过装饰模式来添加滚动条、边框、背景色等功能,而无需修改原始文本框类的代码。


 Web开发中的过滤器

 在Web开发中,过滤器常常用于对请求或响应进行处理,比如身份验证、日志记录、数据压缩等。使用装饰模式可以轻松地添加新的过滤功能,同时保持代码的灵活性和可维护性。


这些都是装饰模式在实际应用中的经典场景

示例说明使用装饰模式的好处

#include <iostream>
#include <memory>

// 抽象组件
class Component {
public:
    virtual void operation() = 0;
    virtual ~Component() {}
};

// 具体组件
class ConcreteComponent : public Component {
public:
    virtual void operation() override {
        std::cout << "ConcreteComponent operation\n";
    }
};

// 抽象装饰器
class Decorator : public Component {
protected:
    std::shared_ptr<Component> component;

public:
    Decorator(std::shared_ptr<Component> comp) : component(comp) {}

    virtual void operation() override {
        if (component != nullptr) {
            component->operation();
        }
    }
};

// 具体装饰器 A
class ConcreteDecoratorA : public Decorator {
public:
    ConcreteDecoratorA(std::shared_ptr<Component> comp) : Decorator(comp) {} //这里初始化了基类 

    virtual void operation() override {
        Decorator::operation();// 调用了基类的public函数
        addedBehavior();
    }

    void addedBehavior() {
        std::cout << "Added Behavior by ConcreteDecoratorA\n";
    }
};

// 具体装饰器 B
class ConcreteDecoratorB : public Decorator {
public:
    ConcreteDecoratorB(std::shared_ptr<Component> comp) : Decorator(comp) {}

    virtual void operation() override {
        Decorator::operation();
        addedState();
    }

    void addedState() {
        std::cout << "Added State by ConcreteDecoratorB\n";
    }
};

int main() {
    // 创建具体组件对象
    std::shared_ptr<Component> component = std::make_shared<ConcreteComponent>();

    // 添加装饰器 A
    std::shared_ptr<Component> decoratedA = std::make_shared<ConcreteDecoratorA>(component);
    decoratedA->operation();

    std::cout << "------\n";

    // 添加装饰器 B
    std::shared_ptr<Component> decoratedB = std::make_shared<ConcreteDecoratorB>(component);
    decoratedB->operation();

    std::cout << "------\n";

    // 动态组合装饰器 A 和 B
    std::shared_ptr<Component> decoratedAB = std::make_shared<ConcreteDecoratorA>(decoratedB);
    decoratedAB->operation();

    return 0;
}

在这个示例中,我们有一个抽象组件 Component,它定义了操作的接口。然后有一个具体的组件 ConcreteComponent 实现了这个接口。然后我们有一个抽象装饰器 Decorator,它也实现了 Component 接口,并持有一个指向 Component 对象的指针。具体的装饰器类 ConcreteDecoratorAConcreteDecoratorB 继承自 Decorator,并添加了额外的行为或状态。

main() 函数中,我们可以看到如何动态地添加或组合装饰器。通过创建具体组件对象,然后将其传递给不同的装饰器来添加不同的行为或状态。这展示了装饰模式的灵活性和动态性。

编译运行

分析

动态地添加或修改对象的功能

这里实现了灵活添加组件

// 添加装饰器 A
    std::shared_ptr<Component> decoratedA = std::make_shared<ConcreteDecoratorA>(component);
    decoratedA->operation();

避免使用继承导致的类爆炸

 上述例子,通过组件通过添加装饰类的方式,将装饰功能分离,而不需要使用继承或者组合的方式,解耦了功能的扩展,新的装饰添加时,不需要改动组件,只要添加新的装饰类即可,避免了需要不断继承带来的风险和大量继承类。

保持类的简单性和单一责任原则

这里通过将装饰功能的分离,保持类的简单性和单一责任原则。

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

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

相关文章

设计模式(六)代理模式

相关文章设计模式系列 1.代理模式简介 代理模式介绍 代理模式也叫委托模式&#xff0c;是结构型设计模式的一种。在现实生活中我们用到类似代理模式的场景有很多&#xff0c;比如代购、代理上网、打官司等。 定义 为其他对象提供一种代理以控制这个对象的访问。 代理模式…

老吕在CSDN写文章之必学【Markdown编辑器教程】

老吕在CSDN写文章之必学【Markdown编辑器教程】 一、作者前言二、介绍Markdown1.Markdown简介2.Markdown优势2.1 使用 Markdown 的优点 3.Markdown发展历程3.1 标准化3.2 CommonMark3.3 GFM3.4 Markdown Extra 4.Markdown应用场景4.1 在线阅读4.2 文本编辑 5.Markdown适用人群 …

搜维尔科技:第九届元宇宙数字人大赛,参赛小组报名确认公告

各位参赛选手大家好&#xff0c;近期已收到新增报名信息如下表&#xff0c;请各位参赛选手确认&#xff0c;如果信息有误或信息不完整请电话联系赛务组工作人员进行更正 随着元宇宙时代的来临&#xff0c;数字人设计成为了创新前沿领域之一。为了提高大学生元宇宙虚拟人角色策划…

js:通过input标签或Drag拖拽文件实现浏览器文件上传获取File文件对象

文档 https://developer.mozilla.org/zh-CN/docs/Web/API/Filehttps://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement/drag_event 通过读取文件可以获取File对象的信息 lastModified: 1707210706000 lastModifiedDate: Tue Feb 06 2024 17:11:46 GMT0800 (中国标准…

pthread_exit和pehread_join函数

pthread_exit&#xff1a; 在线程中禁止调用exit函数&#xff0c;否则会导致整个进程退出&#xff0c;取而代之的是调用pthread_exit函数&#xff0c;这个函数只会使一个线程退出&#xff0c;如果主线程使用pthread_exit函数也不会使整个进程退出&#xff0c;不会影响其他线程…

uniapp播放mp4省流方案

背景&#xff1a; 因为项目要播放一个宣传和讲解视频&#xff0c;视频文件过大&#xff0c;同时还为了节省存储流量&#xff0c;想到了一个方案&#xff0c;用m3u8切片替代mp4。 m3u8&#xff1a;切片播放&#xff0c;可以理解为一个1G的视频文件&#xff0c;自行设置文…

进程间通信:共享内存与信号灯集(2024/2/26)

作业1&#xff1a;共享内存 shmsnd.c: #include <myhead.h> #define PAGE_SIZE 4096int main(int argc, const char *argv[]) {//一、创建key值key_t key-1;if((keyftok("/",k))-1){perror("ftok error");return -1;}//二、根据key值创建共享内存i…

JS防抖函数

场景 频繁触发耗时操作&#xff0c;仅关心最后一次的触发时使用防抖函数 代码 function debounce(fn , delay){let timer;return () > {clearTimeout(timer);timer setTimerout(()>{fn()},delay)} } 详解 触发一次函数&#xff0c;然后执行后续操作 function deboun…

matplotlib plt.show()却弹出空白框并之后自动退出程序的原因及解决方法

运行下列代码并使用plt.show()进行展示时候&#xff0c;cmd输出如下&#xff1a; 先弹出空白框&#xff1a; 而后直接退出程序&#xff1a; 之前遇到过很多次&#xff0c;由于不输出Traceback&#xff0c;完全不知道什么原因。结果发现是因为没有导入torch导致的。 解决办法就…

【MySQL】学习和总结联合查询

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

蜘蛛蜂优化算法SWO求解不闭合MD-MTSP,可以修改旅行商个数及起点(提供MATLAB代码)

1、蜘蛛蜂优化算法SWO 蜘蛛蜂优化算法&#xff08;Spider wasp optimizer&#xff0c;SWO&#xff09;由Mohamed Abdel-Basset等人于2023年提出&#xff0c;该算法模型雌性蜘蛛蜂的狩猎、筑巢和交配行为&#xff0c;具有搜索速度快&#xff0c;求解精度高的优势。VRPTW&#x…

微信小程序本地开发

微信小程序本地开发时不需要在小程序后台配置服务器域名直接在小程序项目中填写后端在本机的IP地址和端口号 如图&#xff08;第一步&#xff09; 填写地址后发现报错&#xff0c;url不是合法域名&#xff0c;则在详情设置不校验合法域名 如图&#xff08;第二歩&#xff09;…

如何本地部署LightPicture结合cpolar内网穿透打造个人云图床

文章目录 1.前言2. Lightpicture网站搭建2.1. Lightpicture下载和安装2.2. Lightpicture网页测试2.3.cpolar的安装和注册 3.本地网页发布3.1.Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1.前言 现在的手机越来越先进&#xff0c;功能也越来越多&#xff0c;而手机…

VM-UNet:视觉Mamba UNet用来医学图像分割 论文及代码解读

VM-UNet 期刊分析摘要贡献方法整体框架1. Vision Mamba UNet (VM-UNet)2. VSS block3. Loss function 实验1.对比实验2.消融实验 可借鉴参考代码使用 期刊分析 期刊名&#xff1a; Arxiv 论文主页&#xff1a; VM-UNet 代码&#xff1a; Code 摘要 在医学图像分割领域&#x…

Python 读取创建word文档

本篇文章内容为使用python 读取word文档和创建word文档 读取doc文件 引入类库 示例如下&#xff1a; import win32com import win32com.client import os 读取doc文件 通过得到的doc文件路径调用系统word功能。 打开文件获取其中的文本信息&#xff0c;输出文本信息&#…

【Java程序员面试专栏 算法思维】五 高频面试算法题:贪心算法

一轮的算法训练完成后,对相关的题目有了一个初步理解了,接下来进行专题训练,以下这些题目就是汇总的高频题目,本篇主要聊聊贪心算法,所以放到一篇Blog中集中练习 题目关键字解题思路时间空间买卖股票的最佳时机 II贪心算法遍历整个股票交易日价格列表 price,并执行贪心策…

算法-计算机基础知识

1&#xff0c;坐标系与数学不同&#xff0c;x轴向下&#xff0c;y轴向右 2.案例&#xff1a;螺旋矩阵 力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 class Solution {public List<Integer> spiralOrder(int[][] matrix) { List<Integer&…

Android 面试问题 2024 版(其三)

Android 面试问题 2024 版&#xff08;其三&#xff09; 十一、版本控制十二、Play 商店和应用程序部署十三、无障碍十四、第三方库和 API十五、解决问题的能力十六、基于 JD 的非常高级别的问题 十一、版本控制 什么是版本控制&#xff0c;为什么它在软件开发中很重要&#x…

sockaddr结构体、sockaddr_in结构体和gethostbyname函数

sockaddr结构体 sockaddr结构体是在网络编程中经常使用的一个数据结构&#xff0c;用来表示套接字地址。它是一个通用的地址结构&#xff0c;可以用于不同的协议&#xff08;如IPv4、IPv6、UNIX等&#xff09;。 在C语言中&#xff0c;sockaddr结构体定义如下&#xff1a; s…

揭密字节跳动薪资职级,资深测试居然能拿......

曾经的互联网是PC的时代&#xff0c;随着智能手机的普及&#xff0c;移动互联网开始飞速崛起。而字节跳动抓住了这波机遇&#xff0c;2015年&#xff0c;字节跳动全面加码短视频&#xff0c;从那以后&#xff0c;抖音成为了字节跳动用户、收入和估值的最大增长引擎。 自从字节逐…