装饰模式(Decorator Pattern)

news2024/11/15 1:50:19

定义

装饰模式(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.coloradmin.cn/o/1464560.html

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

相关文章

设置主从复制时发生报错Could not find first log file name in binary log index file‘;解决方案

如图所示&#xff0c;slave_io_runnind:no,slave_sql_running:yes 此时&#xff0c;主从配置错误&#xff0c;我们可以查看Last_IO_Error:来查看报错信息 此时&#xff0c;我们需要停止从服务器的主从服务&#xff0c; mysql> stop slave; Query OK, 0 rows affected, 1 w…

【开源】JAVA+Vue.js实现医院门诊预约挂号系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 功能性需求2.1.1 数据中心模块2.1.2 科室医生档案模块2.1.3 预约挂号模块2.1.4 医院时政模块 2.2 可行性分析2.2.1 可靠性2.2.2 易用性2.2.3 维护性 三、数据库设计3.1 用户表3.2 科室档案表3.3 医生档案表3.4 医生放号…

VantUI组件的安装和使用

Vant UI 是一款轻量、可靠的移动端 Vue 组件库&#xff0c;适用于构建高性能的移动端页面。它提供了丰富的组件&#xff0c;如按钮、输入框、弹窗、轮播等&#xff0c;并且具有灵活的配置和扩展性。Vant UI 的设计风格简洁&#xff0c;易于上手&#xff0c;能够满足大部分移动端…

手拉手Vite+Vue3+TinyVue+Echarts+TailwindCSS

技术栈springboot3hutool-alloshi-coreVue3viteTinyVueEchartsTailwindCSS软件版本IDEAIntelliJ IDEA 2022.2.1JDK17Spring Boot3.1hutool-all5.8.18oshi-core6.4.1Vue35.0.10vite5.0.10axios1.6.7echarts5.4.3 ECharts是一个使用 JavaScript 实现的开源可视化库&#xff0c;可…

【网络编程】okhttp深入理解

newCall 实际上是创建了一个 RealCall 有三个参数&#xff1a;OkHttpClient&#xff08;通用配置&#xff0c;超时时间等&#xff09; Request(Http请求所用到的条件&#xff0c;url等) 布尔变量forWebSocket&#xff08;webSocket是一种应用层的交互方式&#xff0c;可双向交互…

PolarDN MISC做题笔记

cat flag 使用01打开flag.png,发现图片尾部有padding的数据。D0 CF 11 E0 A1 B1 1A E1为office2007以前版本的文件头。将其另存为flag.doc,打开发现提示需要密码。&#xff08;可以注意到&#xff1a;D0CF11E0非常类似DOCFILE&#xff09; 使用john的office2john.py 提取hash …

React基础-webpack+creact-react-app创建项目

学习视频&#xff1a;学习视频 2节&#xff1a;webpack工程化创建项目 2.1.webpack工程化工具&#xff1a;vite/rollup/turbopak; 实现组件的合并、压缩、打包等&#xff1b; 代码编译、兼容、校验等&#xff1b; 2.2.React工程化/组件开发 我们可以基于webpack自己去搭建…

android studio模拟器不能打开

Andriod:The selected AVD is currently running in the Emulator. Please exit the emulator instance… 1.点击 2.删除下面文件 3.重新打开即可 参考

蓝桥杯倒计时49天!前缀和的拓展

倒计时49天&#xff01; 前缀和的拓展——压缩矩阵 最大子段和 题目描述 给出一个长度为 n n n 的序列 a a a&#xff0c;选出其中连续且非空的一段使得这段和最大。 输入格式 第一行是一个整数&#xff0c;表示序列的长度 n n n。 第二行有 n n n 个整数&#xff0…

精美的WordPress外贸独立站模板

WordPress外贸独立站主题 简洁实用的WordPress外贸独立站主题&#xff0c;适合时尚服装行业搭建wordpress企业官网使用。 https://www.jianzhanpress.com/?p4999 简洁wordpress独立站模板 绿色精美、简洁大气的wordpress外贸独立网站模板 https://www.jianzhanpress.com/?…

【力扣hot100】刷题笔记Day10

前言 一鼓作气把链表给刷完&#xff01;&#xff01;中等题困难题冲冲冲啊啊啊&#xff01; 25. K 个一组翻转链表 - 力扣&#xff08;LeetCode&#xff09; 模拟 class Solution:def reverseKGroup(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:# 翻转…

跨越千年医学对话:用AI技术解锁中医古籍知识,构建能够精准问答的智能语言模型,成就专业级古籍解读助手(LLAMA)

跨越千年医学对话&#xff1a;用AI技术解锁中医古籍知识&#xff0c;构建能够精准问答的智能语言模型&#xff0c;成就专业级古籍解读助手&#xff08;LLAMA&#xff09; 介绍&#xff1a;首先在 Ziya-LLaMA-13B-V1基线模型的基础上加入中医教材、中医各类网站数据等语料库&am…

day16_ListSet课后练习题 - 参考答案

文章目录 day16_课后练习题第1题第2题第3题第4题第5题第6题第7题第8题 day16_课后练习题 第1题 案例&#xff1a; ​ 1、用一个String[]数组存点数 ​ 2、用一个String[]数组存花色 ​ 3、用一个String[]数组存大王、小王 ​ 4、用上面的数组&#xff0c;生成一副扑克牌 …

【寸铁的刷题笔记】树、dfs、bfs、回溯、递归(一)

【寸铁的刷题笔记】树、dfs、bfs、回溯、递归(一) 大家好 我是寸铁&#x1f44a; 总结了一篇刷题关于树、dfs、bfs、回溯、递归的文章✨ 喜欢的小伙伴可以点点关注 &#x1f49d; 105. 从前序与中序遍历序列构造二叉树 模拟分析图 代码实现 /*** Definition for a binary tre…

Android中Transition过渡动画的简单使用

前些天发现了一个蛮有意思的人工智能学习网站,8个字形容一下"通俗易懂&#xff0c;风趣幽默"&#xff0c;感觉非常有意思,忍不住分享一下给大家。 &#x1f449;点击跳转到教程 一、布局xml文件代码如下&#xff1a; <?xml version"1.0" encoding&quo…

websocket与Socket的区别

概念讲解 网络&#xff1a;通俗意义上&#xff0c;也就是连接两台计算器 五层网络模型&#xff1a;应用层、传输层、网络层、数据链路层、物理层 应用层 (application layer)&#xff1a;直接为应用进程提供服务。应用层协议定义的是应用进程间通讯和交互的规则&#xff0c;不…

【结合OpenAI官方文档】解决Chatgpt的API接口请求速率限制

OpenAI API接口请求速率限制 速率限制以五种方式衡量&#xff1a;RPM&#xff08;每分钟请求数&#xff09;、RPD&#xff08;每天请求数&#xff09;、TPM&#xff08;每分钟令牌数&#xff09;、TPD&#xff08;每天令牌数&#xff09;和IPM&#xff08;每分钟图像数&#x…

网页403错误(Spring Security报异常 Encoded password does not look like BCrypt)

这个错误通常表现为"403 Forbidden"或"HTTP Status 403"&#xff0c;它指的是访问资源被服务器理解但拒绝授权。换句话说&#xff0c;服务器可以理解你请求看到的页面&#xff0c;但它拒绝给你权限。 也就是说很可能测试给定的参数有问题&#xff0c;后端…

【rust】vscode下rust-analyzer和Rust Test Lens的Lens

背景 一个粉丝问&#xff1a; 我编辑的launch.json为什么只在按F5的时候工作 按这个debug按钮就不工作&#xff1f; 那在哪改这个插件的配置文档&#xff1f;我一直用的F5 今天上午才注意到这个问题&#xff0c;比如怎么改程序的命令行参数&#xff0c;我意思是如果我非要用…

如何使用逻辑回归处理多标签问题?

逻辑回归处理多分类 1、背景描述2、One vs One 1、背景描述 逻辑回归本身只能用于二分类问题&#xff0c;如果实际情况是多分类的&#xff0c;那么就需要对模型进行一些改动。下面介绍三种常用的将逻辑回归用于多分类的方法 2、One vs One OvO&#xff08;One vs One&#xff…