当前位置: 首页 > news >正文

C++ 装饰器模式

借鉴:https://download.csdn.net/blog/column/12410839/132500767

一、装饰器模式与继承

1.1 装饰器模式与继承的核心区别

  1. 扩展时机

    • 继承:子类在编译时继承父类功能,功能扩展是静态的、不可变的。
    • 装饰器模式:通过组合对象在运行时动态添加功能,支持动态增删职责。
  2. 代码结构

    • 继承:通过子类化实现功能叠加,可能导致类数量爆炸(如需组合多种功能需创建大量子类)。
    • 装饰器模式:通过嵌套装饰器对象实现功能叠加,避免子类膨胀,符合“开闭原则”。
  3. 职责分离

    • 继承:父类与子类功能紧密耦合,修改父类可能影响所有子类。
    • 装饰器模式:核心功能与增强功能分离,装饰器可独立变化而不影响原始对象。

1.2 优缺点对比

特性装饰器模式继承
扩展灵活性✅ 运行时动态增删功能❌ 编译时静态确定功能
代码复杂度⚠️ 需创建多个装饰器类,但避免子类爆炸⚠️ 子类数量可能指数级增长(如组合N种功能需2^N个子类)
性能开销⚠️ 每次调用需转发请求,有轻微性能损耗✅ 直接调用父类方法,无额外开销
适用场景需动态扩展功能(如权限校验、日志记录)功能天然存在层级关系(如动物类→狗类→牧羊犬类)
开闭原则✅ 符合(不修改原类,通过新增装饰器扩展)❌ 不符合(修改父类会影响所有子类)

1.3 关键设计原则体现

  1. 组合优于继承
    装饰器模式通过对象组合实现功能扩展,而非继承层级,符合《设计模式》中“多用组合,少用继承”的建议。

  2. 开闭原则
    原始类对扩展开放(可通过新增装饰器扩展),对修改关闭(无需修改原类代码)。

  3. 单一职责原则
    每个装饰器仅负责添加一种特定功能(如仅处理日志或仅处理缓存),避免类职责过载。

总结:装饰器模式是继承的灵活替代方案,尤其适合需要运行时动态扩展功能且避免子类爆炸的场景,但需权衡其带来的轻微性能开销和代码复杂度。

二、装饰器模式讲解

2.1 定义

装饰模式(Decorator Pattern)是一种结构型设计模式,它允许你在不改变现有对象结构的情况下,动态地将责任(功能)附加到对象上。装饰模式通过以透明的方式扩展对象的功能,比继承更灵活。

装饰模式定义了一个抽象类或接口,用于表示被装饰的对象,并在该抽象类或接口中声明了一些操作方法。同时,还定义了一个具体的装饰器类,该装饰器类包装了一个被装饰的对象,并在调用被装饰对象的操作方法之前或之后执行一些额外的逻辑。

2.2 适用场景

装饰模式适用于以下场景:

Decorator 模式除了采用组合的方式取得了比采用继承方式更好的效果,Decorator 模式还给设计带来一种“即用即付”的方式来添加职责。在 OO 设计和分析经常有这样一种情况:为了多态,通过父类指针指向其具体子类,但是这就带来另外一个问题,这样处于高层的父类就承载了太多的特征(方法),并且继承自这个父类的所有子类都不可避免继承了父类的这些接口,但是可能这并不是这个具体子类所需要的。这样父类就太过臃肿了,那么当需要添加一个操作的时候就可以通过Decorator 模式来解决。

装饰模式适用于以下情况:

  • 当需要在不修改现有对象结构的情况下,动态地给对象添加额外的功能时,可以使用装饰模式。
  • 当需要对多个对象进行灵活的组合和排列,以实现各种功能的组合时,可以使用装饰模式。
  • 当继承关系的扩展性受限,或者不希望使用继承来扩展对象功能时,可以使用装饰模式。

2.3 过程

装饰模式的过程包括以下几个步骤:

  • 定义抽象组件(Component):抽象组件是被装饰对象和装饰器实现的共同接口,它定义了被装饰对象的基本操作。
  • 定义具体组件(Coffee):具体组件是实现了抽象组件接口的被装饰对象,它是装饰模式的核心。
  • 定义抽象装饰器(Decorator):抽象装饰器是一个抽象类或接口,它继承或实现了抽象组件接口,并持有一个对被装饰对象的引用。
  • 定义具体装饰器(Milk、Sugar):具体装饰器是抽象装饰器的实现类,它通过在调用被装饰对象的基本操作前后添加额外的功能来扩展被装饰对象的行为。

2.4 装饰模式类图

关于装饰模式就是在原有基础上一层一层进行包装,对于咖啡Coffee也是如此,不论是Teach 类 还是恶魔果实类的子类Milk、Sugar它们都是Component类的子类,所以咖啡对象是可以为旧的咖啡的,因为在Decorator都绑定了一个实体,他就是咖啡的对象,最终所有咖啡的能力都集中在了这个咖啡对象身上。

2.5 C++示例代码

以下是一个使用装饰模式的示例代码,在这个示例中,我们将以简单的咖啡店场景为例,展示如何使用装饰模式动态地给咖啡添加配料:

#include <iostream> #include <string> // 抽象组件 class Component { public: virtual std::string getDescription() = 0; virtual double getCost() = 0; virtual ~Component() {} }; // 具体组件:咖啡 class Coffee : public Component { public: std::string getDescription() override { return "Coffee"; } double getCost() override { return 1.0; } }; // 抽象装饰器 class Decorator : public Component { protected: Component* m_component; public: Decorator(Component* component) : m_component(component) {} std::string getDescription() override { return m_component->getDescription(); } double getCost() override { return m_component->getCost(); } }; // 具体装饰器:牛奶 class Milk : public Decorator { public: Milk(Component* component) : Decorator(component) {} std::string getDescription() override { return m_component->getDescription() + ", Milk"; } double getCost() override { return m_component->getCost() + 0.5; } }; // 具体装饰器:糖 class Sugar : public Decorator { public: Sugar(Component* component) : Decorator(component) {} std::string getDescription() override { return m_component->getDescription() + ", Sugar"; } double getCost() override { return m_component->getCost() + 0.3; } }; int main() { // 基础咖啡 Component* coffee = new Coffee(); // 加牛奶的咖啡 Component* coffeeWithMilk = new Milk(coffee); std::cout << coffeeWithMilk->getDescription() << std::endl; std::cout << "Cost: $" << coffeeWithMilk->getCost() << std::endl << std::endl; // 加牛奶和糖的咖啡 Component* coffeeWithMilkAndSugar = new Sugar(coffeeWithMilk); // 输出描述和价格 std::cout << coffeeWithMilkAndSugar->getDescription() << std::endl; std::cout << "Cost: $" << coffeeWithMilkAndSugar->getCost() << std::endl; // 清理资源 delete coffee; delete coffeeWithMilk; delete coffeeWithMilkAndSugar; return 0; }

输出

在上述示例中,我们定义了一个抽象组件 Component ,其中包含了获取描述和获取价格的抽象方法。

具体组件 Coffee 实现了 Component 接口,并提供了咖啡的描述和价格。

抽象装饰器 Decorator 继承了 Component 接口,并持有一个对被装饰对象的引用。

具体装饰器 Milk 和 Sugar 分别实现了抽象装饰器,通过在调用被装饰对象的基本操作前后添加额外的描述和价格,来扩展被装饰对象的功能。

在 main 函数中,首先创建了基础的咖啡对象,然后使用装饰器依次给咖啡添加牛奶和糖的配料。最后,输出咖啡的描述和价格。

需要注意的是,在代码的最后,对创建的对象进行了销毁,以释放内存空间。

2.6 使用注意事项

装饰模式通过组合而非继承的方式来扩展对象的功能,避免了类爆炸的问题。但同时也会增加系统的复杂性,需要仔细考虑装饰器的组合方式。
使用装饰模式时,需要注意装饰器的顺序和组合方式,不同的装饰器可能会产生不同的效果。
尽量保持装饰器和被装饰对象之间的接口一致,这样可以使得装饰器和被装饰对象可以互相替换。

http://www.jsqmd.com/news/657799/

相关文章:

  • 模板:效率提升核心工具的选型指南与实用场景汇总
  • 空洞骑士模组管理终极指南:Scarab一键安装与智能依赖解析
  • 告别近似!用MATLAB手把手复现SAR波数域WK算法(附完整代码与Stolt插值避坑指南)
  • 3分钟快速安装:Figma中文界面插件终极指南
  • 043.Jetson上使用TensorRT加速YOLO模型推理:从踩坑到丝滑部署
  • 3分钟快速上手:网页转设计稿的终极指南
  • 从零构建HT1621显示驱动:模块化封装与跨平台移植实战
  • 和Agent的幽默对话(纯记录,s-44是个Agent)
  • 别再只会用默认配置了!Hadoop Yarn Capacity Scheduler队列配置实战(附yarn-site.xml示例)
  • ESP32物联网开发终极指南:Arduino核心快速上手实战
  • 别再只看平均值了!用Python的statsmodels库做分位数回归,全面分析数据分布
  • 04华夏之光永存:黄大年茶思屋榜文解法「第7期4题」信道色散补偿方案·双路径解法
  • AI辅助编程之生成测试用例
  • ChatLog:QQ群聊天记录分析完整指南 - 从数据清洗到可视化
  • 设计效率提升:核心方法与常用工具实操指南
  • mysql-使用openclaw自动化安装xenon集群
  • 国民技术 N32G401K8Q7 QFN-32 单片机
  • 终极指南:如何用SuperPoint彻底解决视觉特征提取难题
  • 从零到一:在Jetson Nano上实现自定义YOLOv5模型的TensorRT推理与DeepStream集成
  • STM32调试进阶:在CLion中利用OpenOCD和SVD文件实现外设寄存器可视化调试
  • Multi-Agent 系统的监控与可观测性:指标设计、日志规范与告警策略
  • D3: 团队 AI 成熟度自评模型
  • 别再死记硬背公式了!手把手教你用运放和RC文氏桥搭一个正弦波信号发生器(附Multisim仿真文件)
  • 从“算不准”到“算得准”:强化学习重塑电力量费异常研判
  • 在Linux系统上读取Access数据库的3个实用方案:MDB Tools深度解析
  • 天问Block驱动74HC595:从零到一,新手也能玩转IO扩展
  • PatreonDownloader终极指南:三步搞定创作者内容批量下载
  • 【2026年最新600套毕设项目分享】基于微信小程序的影院选座系统(30086)
  • STM32F103实战:MPU9250 MPL库移植与HAL库驱动详解
  • 从“骗分”到“策略得分”:聊聊OI/NOIP竞赛中那些官方默许的“聪明”写法