装饰器模式(Decorator Pattern)是一种结构型设计模式,它允许向一个现有的对象添加新的功能,同时又不改变其结构。这种模式创建了一个装饰器类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

一、介绍

核心概念
  1. 组件接口(Component Interface):定义了原始对象和装饰器的共同接口,确保装饰器可以替代原始对象。
  2. 具体组件(Concrete Component):实现了组件接口的原始对象,提供基本功能。
  3. 抽象装饰器(Decorator):持有一个组件对象的引用,并实现了组件接口,以便可以装饰其他装饰器或具体组件。
  4. 具体装饰器(Concrete Decorator):扩展了抽象装饰器的功能,为组件添加额外职责。
  5. 装饰器嵌套机制:通过beverage = std::make_shared<Whip>(beverage); / beverage = std::make_unique<Whip>(std::move(beverage))创建装饰器链,调用时会逐层委托到基础组件
关键特性
  • 动态扩展:在运行时而非编译时添加功能。
  • 递归组合:装饰器可以嵌套多层,形成一个责任链。
  • 透明性:客户端无需关心对象是原始组件还是装饰后的组件。
  • 遵循开闭原则:对扩展开放,对修改关闭。
主要优点
  1. 灵活性:比继承更灵活,可以在运行时动态组合功能。
  2. 单一职责:每个装饰器只关注一个特定功能,符合单一职责原则。
  3. 不修改原有代码:遵循开闭原则(对扩展开放,对修改关闭)。
装饰模式的使用时机
  1. 需要动态添加功能:当你需要在运行时为对象添加或删除功能,而不是在编译时通过继承实现。
  2. 避免子类爆炸:当使用继承会导致大量子类(如不同功能组合)时,装饰模式可以替代继承,减少类的数量。
  3. 功能可插拔:当你希望功能可以被独立启用或禁用时,装饰器可以自由组合。

二、实现

装饰器模式通过组合而非继承来扩展功能,为设计提供了更大的灵活性,是替代大量子类继承的有效解决方案。

#include <iostream>
#include <string>
#include <memory>// 抽象组件接口
class Beverage {
public:virtual ~Beverage() = default;virtual std::string getDescription() const = 0;virtual double cost() const = 0;
};// 具体组件
class Espresso : public Beverage {
public:std::string getDescription() const override {return "Espresso";}double cost() const override {return 1.99;}
};// 抽象装饰器
class CondimentDecorator : public Beverage {
protected:std::unique_ptr<Beverage> beverage;public:CondimentDecorator(std::unique_ptr<Beverage> b) : beverage(std::move(b)) {}
};// 具体装饰器
class Mocha : public CondimentDecorator {
public:using CondimentDecorator::CondimentDecorator;std::string getDescription() const override {return beverage->getDescription() + ", Mocha";}double cost() const override {return beverage->cost() + 0.20;}
};class Whip : public CondimentDecorator {
public:using CondimentDecorator::CondimentDecorator;std::string getDescription() const override {return beverage->getDescription() + ", Whip";}double cost() const override {return beverage->cost() + 0.10;}
};int main() {// 创建基础饮料auto beverage = std::make_unique<Espresso>();std::cout << beverage->getDescription() << " $" << beverage->cost() << std::endl;// 添加装饰beverage = std::make_unique<Mocha>(std::move(beverage));beverage = std::make_unique<Whip>(std::move(beverage));std::cout << beverage->getDescription() << " $" << beverage->cost() << std::endl;return 0;
}
典型应用场景
  1. IO流处理:Java的InputStream/OutputStream、Python的file对象包装。
  2. GUI组件:为窗口、按钮等添加滚动条、边框等功能。
  3. 缓存策略:在数据访问层添加缓存装饰器。
  4. 日志与事务:在业务逻辑外围添加日志记录或事务管理。
  5. 游戏角色装备系统:动态为角色添加武器、防具等属性。

三、优化

  1. 隐藏具体装饰器实现。
  2. 简化客户端代码。
  3. 支持更灵活的装饰器组合。
  4. 增强代码可维护性。
#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <functional>// 抽象组件接口
class Beverage {
public:virtual ~Beverage() = default;virtual std::string getDescription() const = 0;virtual double cost() const = 0;
};// 具体组件
class Espresso : public Beverage {
public:std::string getDescription() const override {return "Espresso";}double cost() const override {return 1.99;}
};class HouseBlend : public Beverage {
public:std::string getDescription() const override {return "House Blend Coffee";}double cost() const override {return 0.89;}
};// 抽象装饰器
class CondimentDecorator : public Beverage {
protected:std::shared_ptr<Beverage> beverage;public:CondimentDecorator(std::shared_ptr<Beverage> b) : beverage(std::move(b)) {}
};// 具体装饰器
class Mocha : public CondimentDecorator {
private:int shots;public:Mocha(std::shared_ptr<Beverage> b, int s = 1) : CondimentDecorator(std::move(b)), shots(s) {}std::string getDescription() const override {return beverage->getDescription() + ", Mocha(" + std::to_string(shots) + ")";}double cost() const override {return beverage->cost() + 0.20 * shots;}
};class Whip : public CondimentDecorator {
public:using CondimentDecorator::CondimentDecorator;std::string getDescription() const override {return beverage->getDescription() + ", Whip";}double cost() const override {return beverage->cost() + 0.10;}
};// 装饰器工厂
enum class Condiment {MOCHA, WHIP
};class BeverageFactory {
public:static std::shared_ptr<Beverage> addCondiment(std::shared_ptr<Beverage> bev, Condiment condiment,int param = 1) {switch(condiment) {case Condiment::MOCHA:return std::make_shared<Mocha>(bev, param);case Condiment::WHIP:return std::make_shared<Whip>(bev);default:return bev;}}
};// 构建器模式
class BeverageBuilder {
private:std::shared_ptr<Beverage> beverage;public:explicit BeverageBuilder(std::shared_ptr<Beverage> base) : beverage(std::move(base)) {}BeverageBuilder& addMocha(int shots = 1) {beverage = std::make_shared<Mocha>(beverage, shots);return *this;}BeverageBuilder& addWhip() {beverage = std::make_shared<Whip>(beverage);return *this;}std::shared_ptr<Beverage> build() {return beverage;}
};int main() {// 使用工厂模式auto beverage1 = std::make_shared<Espresso>();beverage1 = BeverageFactory::addCondiment(beverage1, Condiment::MOCHA, 2);beverage1 = BeverageFactory::addCondiment(beverage1, Condiment::WHIP);std::cout << beverage1->getDescription() << " $" << beverage1->cost() << std::endl;// 使用构建器模式auto beverage2 = BeverageBuilder(std::make_shared<HouseBlend>()).addMocha().addWhip().addMocha().build();std::cout << beverage2->getDescription() << " $" << beverage2->cost() << std::endl;return 0;
}
优化措施
  1. 通过工厂模式封装具体装饰器
// 1. 创建装饰器工厂
enum class Condiment {MOCHA, WHIP
};class BeverageFactory {
public:static std::unique_ptr<Beverage> addCondiment(std::unique_ptr<Beverage> bev, Condiment condiment) {switch(condiment) {case Condiment::MOCHA:return std::make_unique<Mocha>(std::move(bev));case Condiment::WHIP:return std::make_unique<Whip>(std::move(bev));default:return bev;}}
};
  1. 使用引用计数智能指针
using BeveragePtr = std::shared_ptr<Beverage>;
// 避免所有权转移问题,允许多处引用同一对象
  1. 构建器模式支持批量添加装饰器
// 构建器模式示例
class BeverageBuilder {
private:BeveragePtr beverage;public:explicit BeverageBuilder(BeveragePtr base) : beverage(std::move(base)) {}BeverageBuilder& addMocha() {beverage = std::make_shared<Mocha>(beverage);return *this;}BeverageBuilder& addWhip() {beverage = std::make_shared<Whip>(beverage);return *this;}BeveragePtr build() {return beverage;}
};// 使用方式
auto beverage = BeverageBuilder(std::make_shared<Espresso>()).addMocha().addWhip().build();
  1. 添加装饰器链验证
// 在装饰器构造函数中添加验证逻辑
Mocha(std::unique_ptr<Beverage> b) : CondimentDecorator(std::move(b)) {// 可以添加对b类型的检查,防止非法组合
}
  1. 参数化装饰器
class Mocha : public CondimentDecorator {
private:int shots; // 支持多份摩卡public:Mocha(std::unique_ptr<Beverage> b, int s = 1) : CondimentDecorator(std::move(b)), shots(s) {}double cost() const override {return beverage->cost() + 0.20 * shots;}
};
  1. 动态移除装饰器(需要扩展接口):
class RemovableCondiment : public CondimentDecorator {
public:using CondimentDecorator::CondimentDecorator;virtual std::unique_ptr<Beverage> remove() = 0;
};

四、附录

std::shared_ptr 与 std::unique_ptr的区别
1. std::make_unique(std::move(beverage))
  • 独占所有权unique_ptr表示独占所有权,同一对象只能有一个指针指向它
  • 所有权转移:必须使用std::move转移所有权,原指针将变为空
  • 不可复制:无法复制unique_ptr,只能移动
2. std::make_shared(beverage)
  • 共享所有权shared_ptr使用引用计数,允许多个指针共享同一对象
  • 自动内存管理:当最后一个shared_ptr被销毁时,对象才被释放
  • 可复制:可以复制shared_ptr,引用计数增加
3. 关键区别对比
特性std::unique_ptrstd::shared_ptr
所有权独占共享
转移方式必须使用std::move直接复制
原指针状态转移后变为空仍然有效,引用计数增加
内存释放时机指针被销毁时最后一个指针被销毁时
线程安全性无特殊同步引用计数操作是线程安全的
性能轻量级,无引用计数开销有引用计数开销
适用场景对象所有权清晰,避免共享需要多个所有者或生命周期管理复杂的场景
4. 示例对比
// 使用unique_ptr(原示例)
auto beverage = std::make_unique<Espresso>();
beverage = std::make_unique<Mocha>(std::move(beverage)); // 原指针失效// 使用shared_ptr
auto beverage = std::make_shared<Espresso>();
auto whippedBeverage = std::make_shared<Whip>(beverage); // 原指针仍有效
委托链
1、内存布局与对象关系

通过内存布局和函数调用栈来理解。执行以下代码:

auto beverage = std::make_shared<Espresso>(); // 步骤1
beverage = std::make_shared<Whip>(beverage);  // 步骤2
beverage = std::make_shared<Mocha>(beverage); // 步骤3
  1. 步骤1后的内存状态
栈内存                  堆内存
+--------+            +-------------+
| beverage|---------->| Espresso对象 |
+--------+            +-------------+
  1. 2.步骤2后的内存状态
栈内存                  堆内存
+--------+            +-------------+
| beverage|---------->| Whip对象    |
+--------+            +-------------+| beverage_ |+---------->| Espresso对象 |+-------------+
  1. 步骤3后的内存状态
栈内存                  堆内存
+--------+            +-------------+
| beverage|---------->| Mocha对象   |
+--------+            +-------------+| beverage_ |+---------->| Whip对象    |+-------------+| beverage_ |+---------->| Espresso对象 |+-------------+
2、函数调用的底层机制

当我们调用beverage->cost()时,实际发生的是:

  1. 静态类型与动态类型

    • beverage的静态类型是Beverage*(基类指针)
    • 动态类型是Mocha(运行时实际指向的对象类型)
  2. 虚函数表(VTable)

    • 每个包含虚函数的类都有一个虚函数表
    • Mocha的虚函数表中,cost()条目指向Mocha::cost()实现
  3. 调用栈展开

// 调用 beverage->cost()
Mocha::cost() { // 动态类型是Mocha,通过VTable找到此函数return beverage_->cost() + 0.20; // beverage_指向Whip对象// ↑ 此处发生多态调用
}// 执行 Whip::cost()
Whip::cost() { // 通过Whip对象的VTable找到此函数return beverage_->cost() + 0.10; // beverage_指向Espresso对象// ↑ 再次发生多态调用
}// 执行 Espresso::cost()
Espresso::cost() { // 通过Espresso对象的VTable找到此函数return 1.99;
}
3、与递归的本质区别
  1. 递归调用
// 错误示例:直接递归调用自身
Mocha::cost() {return cost() + 0.20; // 无限递归,栈溢出
}
  1. 装饰器模式的委托调用
// 正确实现:通过基类接口调用
Mocha::cost() {return beverage_->cost() + 0.20; // 调用的是另一个对象的cost()
}
4、验证装饰器链
auto base = std::make_shared<Espresso>();
std::cout << "Base addr: " << base.get() << "\n";auto withWhip = std::make_shared<Whip>(base);
std::cout << "Whip addr: " << withWhip.get() << "\n";
std::cout << "Whip's inner addr: " << withWhip->getInnerAddr() << "\n";// 在Whip类中添加辅助方法
class Whip : public CondimentDecorator {
public:Beverage* getInnerAddr() const { return beverage_.get(); }
};

输出结果类似:

Base addr: 0x12345678
Whip addr: 0x87654321
Whip's inner addr: 0x12345678  // 与base地址相同
5、性能
  1. 调用开销
  • 每次装饰器调用涉及一次虚函数调用(通常1-2个指令周期)
  • 多层装饰会增加调用链长度,但现代CPU对此类优化较好
  1. 内存布局
  • 每个装饰器增加一个指针大小的内存开销
  • 对象在堆上非连续分配,可能影响缓存命中率

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

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

相关文章

共指消解技术全解析:从语言学规则到深度学习(附论文精读)

精读威斯康星大学综述《Coreference Resolution: A Survey》&#xff0c;揭秘NLP中"实体链接"的核心技术一、什么是共指消解&#xff1f;为什么它是NLP的基石&#xff1f;共指消解(Coreference Resolution) 旨在识别文本中指向同一实体的不同表述。例如&#xff1a;t…

git配置git commit -m “fix 11,22: 修改bugid为11,22“

文章目录前言一、报错提示二、实现1.commitlint.config.js规范配置2. **修改正则表达式**&#xff1a;3. **移除 scope-case 规则**&#xff1a;4. **增强自定义规则逻辑**&#xff1a;测试结果&#xff1a;正则表达式详解&#xff1a;前言 提示&#xff1a;正常的配置git规范…

nastools继任者?极空间部署影视自动化订阅系统『MediaMaster』

nastools继任者&#xff1f;极空间部署影视自动化订阅系统『MediaMaster』 哈喽小伙伴们好&#xff0c;我是Stark-C~ 对于我们NAS玩家来说&#xff0c;观影总是大家绕不开的一个执念&#xff0c;并且为观影的折腾大家也都是乐此不疲~ 曾经有一个非常绝绝子的观影神器摆在我们…

题解:CF1690G Count the Trains

思路&#xff1a; 首先我们可以理清一下各种情况&#xff1a;1&#xff09;m可能为02&#xff09;一次操作时&#xff0c;只需要考虑每节火车的车头。3&#xff09;当一节火车的速度降低时&#xff0c;只会影响它及它后面的车厢当m0时&#xff0c;我们可以记录上一节车头的速度…

CCF编程能力等级认证GESP—C++3级—20250628

CCF编程能力等级认证GESP—C3级—20250628单选题&#xff08;每题 2 分&#xff0c;共 30 分&#xff09;判断题&#xff08;每题 2 分&#xff0c;共 20 分&#xff09;编程题 (每题 25 分&#xff0c;共 50 分)奇偶校验分糖果单选题&#xff08;每题 2 分&#xff0c;共 30 分…

2G和3G网络关闭/退网状态(截止2025年7月)

从能打语音电话的2G&#xff0c;到能发彩信、聊QQ的3G&#xff0c;这两项陪伴了我们数十年的通信技术&#xff0c;正在悄然退出历史舞台。近日&#xff0c;全球移动供应商协会&#xff08;GSA&#xff09;发布的《2025年7月2G和3G网络关闭报告》显示&#xff0c;全球已有超百个…

Day06_C语言网络编程20250718mobus重点

01.思维导图1 什么是 modbus他是一个在工控领域非常好用的通信写 modbus协议本质上是一个 基于 tcp 协议二次封装的一个协议 什么叫做基于tcp二次封装的协议&#xff1a;我们自己写的pack_t(无论静态还是动态)&#xff0c;都是属于二次封装的协议modbus协议是一种 “主从问答式…

比亚迪古德伍德亮相:从技术突破到文化对话

近日&#xff0c;比亚迪携腾势Z9GT、方程豹豹5、腾势D9亮相英国古德伍德速度节——全球最具声望的汽车文化盛典。方程豹豹5搭载全球首个 DMO电驱越野平台&#xff0c;在爬山赛道上展现出媲美性能跑车的动力响应与精准控制&#xff0c;彻底打破“越野必靠大排量燃油机”的西方传…

UniApp TabBar 用户头像方案:绕过原生限制的实践

需求场景&#xff1a; 在 UniApp 项目中&#xff0c;需要将 TabBar 首页项 (index) 的图标替换为当前用户的网络图片&#xff0c;并实现&#xff1a; 放大且圆形显示。点击该图标时&#xff0c;页面滚动回顶部。切换到其他分类时&#xff0c;首页 Tab 项恢复为普通首页图标。 尝…

如何阅读Spring源码

如何阅读Spring源码 简介 最近有许多人问我如何阅读Spring源码&#xff0c;那我便在这给出阅读源码的方法&#xff0c;能够保证本地能够让源码能够运行起来。 Spring 源码环境本地编译 Gradle下载地址 通过网盘分享的文件&#xff1a;gradle-6.4.1-all.zip 链接: https://pan.b…

Excel导出实战:从入门到精通 - 构建专业级数据报表的完整指南

文章目录Excel导出实战&#xff1a;从入门到精通 - 构建专业级数据报表的完整指南引言&#xff1a;ExcelJSFileSaver如何映射到Excel操作一、ExcelJS核心架构解析 - 从文件结构理解1. 工作簿(Workbook)模型 - 相当于整个Excel文件2. 工作表(Worksheet)配置 - 相当于单个工作表设…

PyTorch图像预处理全解析(transforms)

1. 引言在深度学习计算机视觉任务中&#xff0c;数据预处理和数据增强是模型训练的关键步骤&#xff0c;直接影响模型的泛化能力和最终性能表现。PyTorch 提供的 torchvision.transforms 模块&#xff0c;封装了丰富的图像变换方法&#xff0c;能够高效地完成图像标准化、裁剪、…

slam中的eskf观测矩阵推导

在之前的《slam中的eskf推导》一文中&#xff0c;没有写观测矩阵 H 矩阵的过程&#xff0c;现在补上这部分。前置列举几个等下推导需要用到的一些点&#xff1a;平面特征点构造观测矩阵例如在 fastlio 中&#xff0c;是利用平面特征点到拟合平面的距离来构造观测方程&#xff0…

Python_2

逻辑判断 首先得首先&#xff0c;我们想判断一个逻辑的正确与否&#xff0c;一定是需要一个能够表现出逻辑的词 如果我只说一个1 2&#xff0c;那么大家都不知道我在说什么但是如果我说1<2,那么大家就能判断这个语句的正确与否了 下面是几个常用的逻辑词 < 小于>大于&…

Liunx-Lvs配置项目练习

1.实验环境配置Lvs调度器有两块网卡 一块仅主机和一块nat网卡&#xff0c;客户端nat模式&#xff0c;两台服务器为仅主机模式2.集群和分布式简介集群与分布式系统简介集群 (Cluster)集群是指将多台计算机(通常为同构的)通过高速网络连接起来&#xff0c;作为一个整体对外提供服…

T5(Text-to-Text Transfer Transformer) 模型

下面是对 T5&#xff08;Text-to-Text Transfer Transformer&#xff09; 模型的详细介绍&#xff0c;包括其原理、架构、训练方式、优势与局限&#xff0c;以及与其他模型&#xff08;如 BERT、GPT&#xff09;的对比。一、T5 是什么&#xff1f;T5&#xff08;Text-to-Text T…

PostgreSQL技术大讲堂 - 第97讲:PG数据库编码和区域(locale)答疑解惑

PostgreSQL从入门到精通系列课程&#xff0c;近100节PG技术讲解&#xff0c;让你从小白一步步成长为独当一面的PG专业人员&#xff0c;点击这里查看章节内容。 PostgreSQL从入门到精通课程&#xff0c;持续更新&#xff0c;欢迎加入。第97讲&#xff1a;PostgreSQL 数据库编码…

【IEEE独立出版 】第六届机器学习与计算机应用国际学术会议(ICMLCA 2025)

第六届机器学习与计算机应用国际学术会议&#xff08;ICMLCA 2025&#xff09; 大会简介 第六届机器学习与计算机应用国际学术会议(ICMLCA 2025)定于2025年10月17-19日在中国深圳隆重举行。本届会议将主要关注机器学习和计算机应用面临的新的挑战问题和研究方向&#xff0c;着力…

对于编码电机-520直流减速电机

编码电机的介绍 编码器是一种将角位移或者直线位移转换成一连串电数字脉冲的一种传感器。我们可以通过编码器测量电机转动的位移或者速度信息。 编码器按照工作原理&#xff0c;可以分为增量式编码器和绝对式编码器&#xff0c;绝对式编码器的每一个位置对应一个确定的数字码&a…

Rust入门之并发编程基础(三)

Rust入门之并发编程基础&#xff08;三&#xff09; 题记&#xff1a;6月底7月初&#xff0c;结束北京的工作生活回到二线省会城市发展了&#xff0c;鸽了较久了&#xff0c;要继续坚持学习Rust&#xff0c;坚持写博客。 背景 我们平时使用计算机完成某项工作的时候&#xf…