C++ 装饰器模式
借鉴https://download.csdn.net/blog/column/12410839/132500767一、装饰器模式与继承1.1 装饰器模式与继承的核心区别扩展时机继承子类在编译时继承父类功能功能扩展是静态的、不可变的。装饰器模式通过组合对象在运行时动态添加功能支持动态增删职责。代码结构继承通过子类化实现功能叠加可能导致类数量爆炸如需组合多种功能需创建大量子类。装饰器模式通过嵌套装饰器对象实现功能叠加避免子类膨胀符合“开闭原则”。职责分离继承父类与子类功能紧密耦合修改父类可能影响所有子类。装饰器模式核心功能与增强功能分离装饰器可独立变化而不影响原始对象。1.2 优缺点对比特性装饰器模式继承扩展灵活性✅ 运行时动态增删功能❌ 编译时静态确定功能代码复杂度⚠️ 需创建多个装饰器类但避免子类爆炸⚠️ 子类数量可能指数级增长如组合N种功能需2^N个子类性能开销⚠️ 每次调用需转发请求有轻微性能损耗✅ 直接调用父类方法无额外开销适用场景需动态扩展功能如权限校验、日志记录功能天然存在层级关系如动物类→狗类→牧羊犬类开闭原则✅ 符合不修改原类通过新增装饰器扩展❌ 不符合修改父类会影响所有子类1.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 使用注意事项装饰模式通过组合而非继承的方式来扩展对象的功能避免了类爆炸的问题。但同时也会增加系统的复杂性需要仔细考虑装饰器的组合方式。使用装饰模式时需要注意装饰器的顺序和组合方式不同的装饰器可能会产生不同的效果。尽量保持装饰器和被装饰对象之间的接口一致这样可以使得装饰器和被装饰对象可以互相替换。