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

设计模式(C++)详解—装饰器模式(2)

<摘要>
本文是一场关于装饰器模式的盛大巡礼!我们将穿越到软件设计的“蛮荒”时代,亲眼目睹“类爆炸”灾难如何催生了装饰器这一优雅的解决方案。全文以“给核心对象穿衣服”这一核心比喻贯穿始终,通过精致UML图、生动故事、战场对比和深入的技术剖析,全方位解构装饰器模式。你将不仅理解其“组合优于继承”的哲学内核,还能通过咖啡店、超级英雄、游戏装备、IO流等大量有趣案例,透彻掌握其在C++中的实现精髓(包括智能指针、移动语义等现代C++特性)。本文还深入探讨了其在STL、Qt等框架中的现实应用,并与代理、适配器等模式进行犀利对比。最后,一份完整的、可编译运行的代码库(附带详尽注释和Makefile)将为你打通从理论到实践的最后一公里。这是一份旨在让你彻底弄懂、真心爱上装饰器模式的终极指南。


<解析>

1. 背景与核心概念:从“类爆炸”废墟中诞生的救世主

1.1 史诗的开篇:一场名为“继承”的噩梦

想象一下,你是一名软件建筑师,接到了设计一个“咖啡店订单系统”的任务。最初,一切都很简单。

class Coffee {
public:virtual double cost() const { return 5.0; }
};

客户说:“我要加奶的咖啡。” 简单!继承搞定。

class CoffeeWithMilk : public Coffee {
public:double cost() const override { return Coffee::cost() + 2.0; }
};

客户又说:“我还要加糖!” 没问题!

class CoffeeWithMilkAndSugar : public CoffeeWithMilk {
public:double cost() const override { return CoffeeWithMilk::cost() + 1.0; }
};

然而,客户的欲望是无限的:“有没有只加糖的?有没有加奶、加糖还加巧克力的?有没有加双份奶的?……” 你的代码库很快变成了下图所示的噩梦:

Coffee
CoffeeWithMilk
CoffeeWithSugar
CoffeeWithChocolate
CoffeeWithMilkAndSugar
CoffeeWithMilkAndChocolate
CoffeeWithSugarAndChocolate
...
CoffeeWithMilkAndSugarAndChocolate
...
...
...

这就是臭名昭著的 “类爆炸” (Class Explosion)!你的项目被无数的子类淹没,维护起来就像在走一个巨大的迷宫。添加一种新配料?意味着你要创建所有可能组合的新子类。这简直是软件工程界的“指数级灾难”。

1.2 救世主的降临:装饰器模式的华丽转身

就在这绝望之际,GoF(四人帮,Gang of Four)带来了他们的圣经《设计模式》,并揭示了装饰器模式。

它的核心思想简单而深刻:不要再执着于“是什么”(is-a)的继承关系,而是转向“有什么”(has-a)的组合关系。

给对象“穿衣服”:一个贯穿全文的完美比喻

  • 你的身体:就是核心对象(BasicCoffee)。
  • 一件T恤:是一个装饰器(MilkDecorator),它包裹着你的身体,提供了“棉质T恤”的功能。
  • 一件外套:是另一个装饰器(SugarDecorator),它包裹在“T恤”外面,提供了“保暖”和“甜蜜”的功能。
  • 一条围巾:……如此往复。

你可以在运行时随心所欲地穿上或脱下这些“衣服”(装饰器)。最终,从外表看,你还是你(因为它们都实现了Person接口),但你的功能(保暖程度、风格)已经发生了巨大变化。

装饰器模式的四大天王(关键角色):

  1. Component (抽象组件) - Beverage

    • 身份:家族的族长。它定义了整个家族最基本、最核心的契约(接口)。
    • 职责:宣布“所有这个家族的对象,都必须能计算价格(cost())和说出自己的描述(getDescription())”。
    • C++绝技:拥有一个纯虚函数cost() const = 0;,让子类去实现。
  2. ConcreteComponent (具体组件) - Espresso, HouseBlend

    • 身份:家族里的老实人,族长最朴实的儿子。他们实现了核心功能,是我们要被装饰的原始对象。
    • 职责:“我就是一杯纯粹的咖啡,这是我的本来价格和味道。”
    • C++绝技:重写纯虚函数,提供具体的实现。
  3. Decorator (抽象装饰器) - CondimentDecorator

    • 身份:家族的“包装师”。它既是家族的一员(继承自Component),又手里抓着另一个家族成员(组合一个Component指针)。
    • 职责:“我是所有装饰器的老大,我知道怎么持有一个组件,并维护Component的接口。具体的装饰活儿交给我的手下们。”
    • C++心机:它通常不会自己实现cost(),而是留给子类。它持有一个std::unique_ptr<Component>,准备随时进行“装饰”。
  4. ConcreteDecorator (具体装饰器) - MochaDecorator, WhipDecorator

    • 身份:家族的“化妆师”。各怀绝技,负责给对象添加具体的额外职责。
    • 职责:“我包裹一个组件,然后在它的基础上,加上我自己的价格和描述。”
    • C++绝技:在重写的cost()方法中,先调用被包裹对象的cost(),然后加上自己的费用。

让我们用UML图来直观地看看这个神奇的家族关系:

decorates
«abstract»
Beverage
+description: string
+getDescription() : string
+cost()
Espresso
+cost() : double
HouseBlend
+cost() : double
«abstract»
CondimentDecorator
-beverage: unique_ptr<Beverage>
+getDescription()
MochaDecorator
+cost() : double
+getDescription() : string
WhipDecorator
+cost() : double
+getDescription() : string

▲ 装饰器模式标准UML类图(使用Mermaid绘制)

这幅图揭示了装饰器模式的魔法源泉:

  1. 同一接口ConcreteComponentDecorator都是Component的子类。这意味着Decorator可以完美地冒充Component,这就是“透明性”的基石。
  2. 组合关系Decorator手中牢牢抓着一个Component的指针。这个指针可以指向一个ConcreteComponent,也可以指向另一个Decorator。这就允许了装饰器的层层嵌套,形成一条“装饰链”。
  3. 递归思想:当你在最外层的装饰器上调用cost()时,它会将调用委托给内部的Component,内部的Component可能又是一个装饰器,它会继续向内委托,直到调用到达最核心的ConcreteComponent。然后,计算结果会带着每一层装饰器添加的“附加值”,一层层地返回。这个过程就像一颗石子投入水中,涟漪一层层荡开,最后又一层层返回。
1.3 现状与趋势:无处不在的装饰之魂

装饰器模式早已超越了简单的咖啡店例子,其思想深深植根于现代软件开发的方方面面:

  • Java I/O Streams: 这是最经典的案例。FileInputStream(具体组件)被BufferedInputStream(装饰器)装饰,再被DataInputStream(另一个装饰器)装饰,从而动态地添加了缓冲、读取基本数据类型等功能。
  • C++ Standard Template Library (STL): std::stackstd::queue被称为容器适配器(Container Adapters)。它们本质上是对底层容器(如std::deque, std::list)的装饰。它们限制了容器的接口,改变了其行为(如LIFO, FIFO),完美体现了“组合优于继承”。
  • 图形用户界面 (GUI): Qt、Java SWING等框架中,为视觉组件添加边框、滚动条、阴影等,无一不是装饰器模式的用武之地。
  • Web开发: React中的高阶组件 (Higher-Order Components, HOC) 和Python/Java中的装饰器语法(@decorator),是装饰器模式在函数式和注解编程范式下的现代化身。它们在不修改原有组件的情况下,为其注入新的props或行为。

装饰器模式的生命力在于它精准地抓住了“弹性扩展”这一永恒需求,是“开放-封闭原则”(对扩展开放,对修改关闭)最优雅的实践之一。


2. 设计意图与考量:在优雅与复杂间走钢丝

2.1 灵魂拷问:我们为何而战?

GoF对装饰器模式的意图定义是:“动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。”

这句看似平淡的话,背后是无数前辈在“类爆炸”的火海中总结出的智慧结晶。我们来一场深度灵魂拷问:

核心目标意味着什么?如何实现?
动态地 (Dynamically)功能的添加是在运行时决定的,而不是在编译时写死的。通过组合对象指针,在程序运行时动态地组装装饰链。
额外的职责 (Additional Responsibilities)指的是那些可选的、非核心的功能。每个具体装饰器封装一个单一的、明确的职责。
比生成子类更灵活 (More Flexible than Subclassing)彻底击败“类爆炸”,用线性增长的类数应对指数增长的功能组合。利用组合的灵活性,将功能分解为多个小类,再递归地组合它们。
2.2 设计理念:权衡的艺术与智慧的结晶

任何设计都是权衡的产物,装饰器模式也不例外。它用一时的“复杂性”换来了长久的“灵活性”。

设计决策带来的巨大好处 (The Upside)潜在的代价与考量 (The Downside)
1. 装饰器与组件共享同一接口透明性 (Transparency):客户端完全不知道它面对的是一个被层层包裹的“套娃”,它依然快乐地调用着Component的接口。这符合“最少知识原则”。不纯粹的身份:装饰器在“是”一个组件的同时,又“有”一个组件。这有时会让基于类型的操作变得复杂。
2. 采用组合而非继承极致灵活 (Ultimate Flexibility):运行时动态组合,功能无限叠加。完全符合“组合优于继承”的原则。小对象瘟疫:会在系统中引入大量细粒度的装饰器对象,如果过度使用,会使系统变得复杂,难以理解和调试。
3. 装饰器可在被装饰对象行为前后添加新行为强大扩展 (Powerful Extension):可以在调用原始操作(如权限检查)、(如日志记录)甚至中间(如缓冲)插入新行为。顺序依赖症:装饰器的顺序有时至关重要!先加密再压缩,和先压缩再加密,结果是完全不同的。
4. 省略抽象的Decorator类代码更简洁。如果装饰器种类很少,可以直接让具体装饰器继承Component并组合一个Component失去了一个有用的中间层,可能会使未来添加所有装饰器的共通功能变得困难。

为了更生动地展示这个动态组合的过程,我们来看一段“对象时装秀”的时序图:

ClientWhipDecoratorMochaDecoratorMochaDecoratorHouseBlend造型师开始搭配!new() 基础款new(HouseBlend) 加第一份摩卡new(Mocha1) 加第二份摩卡new(Mocha2) 再加奶泡时装秀开始!Client请求报价(cost)cost()计算自己的费用 ($0.10)cost()计算自己的费用 ($0.20)cost()计算自己的费用 ($0.20)cost()计算基础费用 ($0.89)return 0.89return 0.89 + 0.20 = 1.09return 1.09 + 0.20 = 1.29return 1.29 + 0.10 = 1.39总价: $1.39ClientWhipDecoratorMochaDecoratorMochaDecoratorHouseBlend

▲ 装饰器模式调用时序图:一场价格的递归计算(使用Mermaid绘制)

2.3 战场对决:装饰器 vs. 它的“亲戚们”

在设计模式的世界里,分清“谁是谁”至关重要。装饰器有几个长相相似的“亲戚”,但它们的“职业”截然不同。

模式“职业” (目的)“招牌动作” (关键区别)一句话人话
装饰器 (Decorator)增强功能:不改变接口,增加新的职责。像给手机加外壳、贴膜。透明扩展、递归组合。关注于动态、无限地添加功能。“我还是我,只是功能更强了。”
代理 (Proxy)控制访问:控制对对象的访问,可能延迟创建、加权限控制、做网络代理等。对象关系通常较静态,代理通常不或很少增强功能,而是做访问控制。“我替本体出面,控制谁可以见它,或者什么时候见。”
适配器 (Adapter)转换接口:将一个接口转换成另一个客户端期望的接口。像电源转接头。接口转换是核心目的,而不是增强功能。“我不是你想要的,但我能变成你想要的。”
策略 (Strategy)改变行为:封装一系列算法,使它们可以相互替换。改变对象的内核。改变对象的内核算法,而装饰器是在外围增加新的、辅助性的行为。“我换的是它的‘心脏’,而你只是给它穿‘衣服’。”
组合 (Composite)统一处理:将对象组合成树形结构以表示“部分-整体”的层次结构。旨在统一处理单个对象和对象组合,装饰器旨在增强单个对象的功能。“我和装饰器是好朋友,但我是管理‘集团’的,他是包装‘个人’的。”

3. 实例与应用场景:模式的力量,源于解决真实的问题

让我们告别枯燥的咖啡店,看看装饰器模式在更多元、更酷的场景中是如何大显神通的。

实例一:超级英雄装备系统

场景:设计一个游戏中的超级英雄。英雄本身有基础能力,但可以通过装备各种高科技装备(蝙蝠侠战衣、钢铁侠盔甲、美国队长的盾牌)来动态获得新能力。

// Component: 英雄
class Hero {
public:virtual ~Hero() = default;virtual std::string getAbilities() const {return "Fist Fight"; // 基础能力:徒手格斗}virtual int getPower() const {return 10; // 基础战斗力}
};// ConcreteComponent: 布鲁斯·韦恩
class BruceWayne : public Hero {
public:std::string getAbilities() const override {return "Master Martial Artist, Genius-Level Intellect, " + Hero::getAbilities();}// ... 可能还有其他布鲁斯特有的属性
};// Decorator: 装备装饰器
class GearDecorator : public Hero {
protected:std::unique_ptr<Hero> hero;
public:explicit GearDecorator(std::unique_ptr<Hero> h) : hero(std::move(h)) {}// getAbilities 和 getPower 仍然是纯虚的?不,我们可以提供默认实现:直接委托。virtual std::string getAbilities() const override { return hero->getAbilities(); }virtual int getPower() const override { return hero->getPower(); }
};// ConcreteDecorator: 蝙蝠战衣
class BatsuitDecorator : public GearDecorator {
public:using GearDecorator::GearDecorator;std::string getAbilities() const override {return GearDecorator::getAbilities() + ", Advanced Armor, Grappling Hook, Stealth Tech";}int getPower() const override {return GearDecorator::getPower() + 50;}
};// ConcreteDecorator: 便携式飞行器
class JetpackDecorator : public GearDecorator {
public:using GearDecorator::GearDecorator;std::string getAbilities() const override {return GearDecorator::getAbilities() + ", Flight";}int getPower() const override {return GearDecorator::getPower() + 25;}
};// 客户端使用
void createBatman() {std::unique_ptr<Hero> bruce = std::make_unique<BruceWayne>();std::cout << "Bruce: " << bruce->getAbilities() << " | Power: " << bruce->getPower() << std::endl;// 动态装备!bruce = std::make_unique<BatsuitDecorator>(std::move(bruce));bruce = std::make_unique<JetpackDecorator>(std::move(bruce)); // 蝙蝠侠+战衣+飞行器!std::cout << "Batman with Jetpack: " << bruce->getAbilities() << " | Power: " << bruce->getPower() << std::endl;
}

输出:

Bruce: Master Martial Artist, Genius-Level Intellect, Fist Fight | Power: 10
Batman with Jetpack: Master Martial Artist, Genius-Level Intellect, Fist Fight, Advanced Armor, Grappling Hook, Stealth Tech, Flight | Power: 85

这个例子展示了如何动态地“组装”出一个强大的英雄,完美体现了装饰器模式的灵活性。

实例二:数据流处理管道 (I/O流的精髓)

场景:处理网络数据,需要经过解密、解压、校验等步骤。这些步骤的顺序和组合可能需要动态变化。

// Component: 数据处理器
class DataProcessor {
public:virtual ~DataProcessor() = default;virtual std::string process(const std::string& data) = 0;
};// ConcreteComponent: 基础处理器 (可能就是简单读写)
class BasicProcessor : public DataProcessor {
public:std::string process(const std::string& data) override {return data; // 原样返回}
};// Decorator: 处理过滤器
class ProcessingFilter : public DataProcessor {
protected:std::unique_ptr<DataProcessor> processor;
public:explicit ProcessingFilter(std::unique_ptr<DataProcessor> p) : processor(std::move(p)) {}
};// ConcreteDecorator: 解密过滤器
class DecryptionFilter : public ProcessingFilter {std::string key;
public:DecryptionFilter(std::unique_ptr<DataProcessor> p, std::string k) : ProcessingFilter(std::move(p)), key(std::move(k)) {}std::string process(const std::string& data) override {std::string encryptedData = processor->process(data); // 先让内部的处理器处理(如果有的话)return decrypt(encryptedData, key); // 然后自己解密}
private:std::string decrypt(const std::string& data, const std::string& key) { /*...*/ return "Decrypted(" + data + ")"; }
};// ConcreteDecorator: 解压过滤器
class DecompressionFilter : public ProcessingFilter {
public:using ProcessingFilter::ProcessingFilter;std::string process(const std::string& data) override {std::string compressedData = processor->process(data);return decompress(compressedData);}
private:std::string decompress(const std::string& data) { /*...*/ return "Decompressed(" + data + ")"; }
};// 客户端使用:构建处理管道
void processData() {// 构建一个管道:数据 -> 解密 -> 解压 -> 基础处理std::unique_ptr<DataProcessor> processor = std::make_unique<BasicProcessor>();processor = std::make_unique<DecompressionFilter>(std::move(processor));processor = std::make_unique<DecryptionFilter>(std::move(processor), "my_secret_key");std::string rawData = "Xyz...encrypted and compressed data...";std::string result = processor->process(rawData);std::cout << "Final Result: " << result << std::endl;// 输出可能是: Final Result: Decompressed(Decrypted(Xyz...encrypted and compressed data...))
}

这个例子深刻揭示了装饰器模式在构建灵活、可配置的处理管道方面的巨大优势。顺序至关重要!

实例三:GUI组件装饰 (给界面“美颜”)

场景:为UI组件动态添加视觉特效,如边框、滚动条、阴影、背景色等。

// Component: 视觉组件
class VisualComponent {
public:virtual ~VisualComponent() = default;virtual void draw() = 0;
};// ConcreteComponent: 文本框
class TextBox : public VisualComponent {
public:void draw() override {std::cout << "Drawing TextBox content" << std::endl;}
};// Decorator: 视觉装饰器
class VisualDecorator : public VisualComponent {
protected:std::unique_ptr<VisualComponent> component;
public:explicit VisualDecorator(std::unique_ptr<VisualComponent> comp) : component(std::move(comp)) {}void draw() override {component->draw(); // 默认委托}
};// ConcreteDecorator: 边框装饰
class BorderDecorator : public VisualDecorator {int width;std::string color;
public:BorderDecorator(std::unique_ptr<VisualComponent> comp, int w, std::string c): VisualDecorator(std::move(comp)), width(w), color(std::move(c)) {}void draw() override {VisualDecorator::draw(); // 1. 先画内部的组件drawBorder();            // 2. 再画边框(新增功能)}
private:void drawBorder() {std::cout << "Drawing Border: width=" << width << ", color=" << color << std::endl;}
};// ConcreteDecorator: 滚动条装饰
class ScrollDecorator : public VisualDecorator {
public:using VisualDecorator::VisualDecorator;void draw() override {VisualDecorator::draw();drawScrollBar();}
private:void drawScrollBar() {std::cout << "Drawing ScrollBar" << std::endl;}
};// 客户端使用
void createFancyTextBox() {std::unique_ptr<VisualComponent> textBox = std::make_unique<TextBox>();textBox = std::make_unique<BorderDecorator>(std::move(textBox), 2, "red");textBox = std::make_unique<ScrollDecorator>(std::move(textBox));textBox->draw();
}

输出:

Drawing TextBox content
Drawing Border: width=2, color=red
Drawing ScrollBar

GUI装饰是装饰器模式的另一个绝佳应用场景,它让我们可以像搭积木一样构建出复杂的UI效果。


4. C++代码实现:现代C++的优雅实践

让我们用现代C++(C++11/17)的最佳实践,完整地实现最经典的咖啡店例子。

4.1 带完整注释的代码实现

Beverage.h

/*** @file Beverage.h* @brief 装饰器模式核心头文件:定义抽象组件、具体组件、抽象装饰器和具体装饰器。* @details* 使用现代C++特性:智能指针(std::unique_ptr)管理资源,移动语义转移所有权,override明确重写。*/#pragma once // 跨平台防止重复包含#include <string>
#include <memory> // For std::unique_ptr
#include <utility> // For std::move/*** @brief 抽象饮料类(Component)* * 它是所有饮料和调料装饰器的共同基类,定义了整个模式的抽象接口。* 使用虚析构函数确保通过基类指针删除派生类对象时行为正确。*/
class Beverage {
public:virtual ~Beverage() = default; ///< 虚析构函数是多态基类的必需品/*** @brief 获取饮料的描述信息* * 非纯虚函数,提供默认实现。具体组件和装饰器可以重写它。* * @return std::string 描述字符串*/virtual std::string getDescription() const {return description_;}/*** @brief 计算饮料的价格* * 这是一个纯虚函数,强制所有具体子类都必须提供自己的实现。* * @return double 计算出的价格*/virtual double cost() const = 0;protected:std::string description_ = "Unknown Beverage"; ///< 饮料描述, protected 允许子类访问和修改
};/*** @brief 调料装饰器抽象类(Decorator)* * 继承自Beverage,因此它本身也是一种Beverage,可以替代Beverage对象。* 核心在于它组合(持有)了一个Beverage对象的智能指针。* 此类声明了getDescription()为纯虚函数,强制具体装饰器必须重写。*/
class CondimentDecorator : public Beverage {
public:virtual ~CondimentDecorator() override = default; // 虽然不必须,但显式声明是好习惯/*** @brief 获取描述(纯虚函数)* * 在装饰器模式中,装饰器必须修改描述,所以这里声明为纯虚,* 强制每个具体装饰器实现自己的描述追加逻辑。*/virtual std::string getDescription() const override = 0;// cost() 不在这个抽象层实现,留给具体装饰器protected:/*** @brief 被装饰的饮料对象* * 使用std::unique_ptr明确表示所有权:CondimentDecorator独占其持有的Beverage。* 通过构造函数注入,并使用std::move转移所有权,避免不必要的拷贝。*/std::unique_ptr<Beverage> beverage_;
};// --- 具体组件 (Concrete Components) ---/*** @brief 浓缩咖啡*/
class Espresso : public Beverage {
public:Espresso();double cost() const override;
};/*** @brief 黑咖啡*/
class HouseBlend : public Beverage {
public:HouseBlend();double cost() const override;
};// --- 具体装饰器 (Concrete Decorators) ---/*** @brief 摩卡调料装饰器*/
class Mocha : public CondimentDecorator {
public:/*** @brief 构造函数,接收一个需要被装饰的Beverage对象* * @param beverage 被装饰的饮料。注意:一旦传入,其所有权将转移给Mocha对象。*/explicit Mocha(std::unique_ptr<Beverage> beverage);std::string getDescription() const override;double cost() const override;
};/*** @brief 奶泡调料装饰器*/
class Whip : public CondimentDecorator {
public:explicit Whip(std::unique_ptr<Beverage> beverage);std::string getDescription() const override;double cost() const override;
};

Beverage.cpp

/*** @file Beverage.cpp* @brief 实现Beverage.h中声明的所有具体类。*/#include "Beverage.h"// ------------------------- 具体组件实现 -------------------------Espresso::Espresso() {description_ = "Espresso";
}
double Espresso::cost() const {return 1.99;
}HouseBlend::HouseBlend() {description_ = "House Blend Coffee";
}
double HouseBlend::cost() const {return 0.89;
}// ------------------------- 具体装饰器实现 -------------------------Mocha::Mocha(std::unique_ptr<Beverage> beverage) {beverage_ = std::move(beverage); // 接管传入对象的所有权
}
std::string Mocha::getDescription() const {// 在原有描述基础上,追加当前调料的描述。return beverage_->getDescription() + ", Mocha";
}
double Mocha::cost() const {// 计算价格:被装饰对象的价格 + 当前调料的价格。return beverage_->cost() + 0.20;
}Whip::Whip(std::unique_ptr<Beverage> beverage) {beverage_ = std::move(beverage);
}
std::string Whip::getDescription() const {return beverage_->getDescription() + ", Whip";
}
double Whip::cost() const {return beverage_->cost() + 0.10;
}

main.cpp

/*** @file main.cpp* @brief 装饰器模式客户端演示代码* * 展示如何动态地组合各种饮料和调料,并计算最终价格和描述。*/#include <iostream>
#include <memory>
#include "Beverage.h"/*** @brief 打印订单信息* * 辅助函数,用于统一格式化输出饮料信息。* * @param beverage 要打印的饮料对象*/
void printOrder(const Beverage& beverage) {std::cout << "  " << beverage.getDescription() << " -- $" << beverage.cost() << std::endl;
}/*** @brief 主函数* * 程序入口,创建不同的饮料和调料组合,演示装饰器模式的强大功能。* * @return int 程序退出码*/
int main() {std::cout << "\n=== 装饰器模式咖啡店开业啦! ===\n" << std::endl;std::cout << "订单 1: 一杯浓缩咖啡" << std::endl;std::unique_ptr<Beverage> beverage1 = std::make_unique<Espresso>();printOrder(*beverage1);std::cout << "\n订单 2: 一杯双倍摩卡加奶泡的黑咖啡" << std::endl;// 1. 创建基础饮料:黑咖啡std::unique_ptr<Beverage> beverage2 = std::make_unique<HouseBlend>();// 2. 用装饰器进行第一次装饰:加一份摩卡beverage2 = std::make_unique<Mocha>(std::move(beverage2));// 3. 第二次装饰:再加一份摩卡beverage2 = std::make_unique<Mocha>(std::move(beverage2));// 4. 第三次装饰:加奶泡beverage2 = std::make_unique<Whip>(std::move(beverage2));printOrder(*beverage2);std::cout << "\n订单 3: 一杯只加奶泡的浓缩咖啡" << std::endl;// 另一种简洁的写法,但注意:一旦move,之前的指针就无效了。auto beverage3 = std::make_unique<Whip>(std::make_unique<Espresso>());printOrder(*beverage3);std::cout << "\n=== 感谢光临! ===\n" << std::endl;return 0;
}
4.2 编译与运行:Makefile一站式搞定

Makefile

# Decorator Pattern Demo Makefile
# 使用现代C++标准(C++17)并开启常用警告# 编译器配置
CXX := g++
CXXFLAGS := -std=c++17 -Wall -Wextra -pedantic -O2# 最终目标
TARGET := decorator_demo# 需要编译的源文件
SRCS := Beverage.cpp main.cpp
# 由源文件生成的目标文件
OBJS := $(SRCS:.cpp=.o)# 默认目标:构建可执行文件
all: $(TARGET)# 链接规则:将所有的.o文件链接成可执行文件
$(TARGET): $(OBJS)$(CXX) $(CXXFLAGS) -o $@ $^# 编译规则:每个.cpp文件生成一个.o文件
%.o: %.cpp Beverage.h$(CXX) $(CXXFLAGS) -c $< -o $@# 伪目标:运行程序
run: $(TARGET)@echo "运行程序..."@./$(TARGET)# 伪目标:清理生成的文件
clean:rm -f $(OBJS) $(TARGET)# 伪目标:显示帮助信息
help:@echo "可用命令:"@echo "  make all    编译程序(默认)"@echo "  make run    编译并运行程序"@echo "  make clean  清理编译生成的文件"@echo "  make help   显示此帮助信息"# 声明这些目标是“伪目标”,不代表实际文件
.PHONY: all run clean help

编译与运行:

  1. 保存文件:将上述四个文件(Beverage.h, Beverage.cpp, main.cpp, Makefile)放在同一个目录下。
  2. 打开终端:导航到这个目录。
  3. 执行编译
    make
    
    或者
    make all
    
    你会看到g++编译器的输出,如果没有错误,将生成名为decorator_demo的可执行文件。
  4. 运行程序
    make run
    
    或者直接运行:
    ./decorator_demo
    

预期输出与解读:

=== 装饰器模式咖啡店开业啦! ===订单 1: 一杯浓缩咖啡Espresso -- $1.99订单 2: 一杯双倍摩卡加奶泡的黑咖啡House Blend Coffee, Mocha, Mocha, Whip -- $1.39订单 3: 一杯只加奶泡的浓缩咖啡Espresso, Whip -- $2.09=== 感谢光临! ===
  • 订单1:验证了基础组件Espresso的正确性。
  • 订单2:是装饰器模式的精髓展示。描述清晰地显示了装饰的层次(House Blend Coffee -> Mocha -> Mocha -> Whip),价格也是正确的:0.89 + 0.20 + 0.20 + 0.10 = $1.39
  • 订单3:展示了另一种组合方式,证明装饰器可以装饰任何Beverage,包括另一个具体组件。价格:1.99 + 0.10 = $2.09

这个完整的示例不仅实现了模式,还展示了现代C++中资源管理(智能指针)、所有权转移(移动语义)和代码组织的最佳实践。


5. 总结

装饰器模式是一种极其强大且灵活的结构型设计模式,是“组合优于继承”这一设计原则的典范体现。它通过将功能分解为一系列装饰器类,并通过递归组合的方式动态地构建对象,完美地解决了使用继承带来的“类爆炸”和灵活性不足的问题。

它的核心价值在于:

  • 动态扩展:允许在运行时为对象添加或撤销职责。
  • 透明性:装饰后的对象与原始对象接口一致,客户端无需更改代码。
  • 避免子类泛滥:用少量装饰器类组合出无限的功能可能性。

在C++中实现它,需要注意:

  • **使用智能指针(如std::unique_ptr)**来明确管理被装饰对象的所有权,避免内存泄漏。
  • 利用移动语义来高效地转移资源所有权,构建装饰链。
  • 理解override和虚析构函数在多态中的重要性。

装饰器模式并非银弹,它会增加系统的复杂性,引入大量小对象。但在需要弹性、可扩展设计的场景下(如I/O流、GUI组件、中间件管道),它无疑是首选方案。从《设计模式》到现代C++和前端框架,它的思想历久弥新,深刻理解并掌握它,将极大地提升你作为软件工程师的设计和架构能力。

http://www.dtcms.com/a/390228.html

相关文章:

  • 复旦×明略×秒针:用AIGD把经验决策变科学决策
  • Apache SeaTunnel 2.3.12 发布!核心引擎升级、连接器生态再扩张
  • Java中存在哪些锁?
  • 非连续性内存分配:分页
  • [x-cmd] x-cmd 性能
  • Zynq开发实践(SDK之定时器)
  • Java IO核心知识提问点
  • 微前端--前端架构的模块化革命
  • SQL分析-基础
  • V821---4M高集成无线视频芯片
  • count down 92 days
  • 学习日记-JS+DOM-day58-9.18
  • 【Python】基于界面库PyQt5+QTLinguist的多语言切换功能实现
  • Flutter 组件介绍:TickerMode
  • SQL 聚合函数总结:COUNT、SUM、AVG、MAX、MIN 详解
  • 资深专业新媒体营销数字营销培训老师商学院教授课程老师培训讲师唐兴通讲授10大经典社群私域案例:Lululemon的热汗式信仰社群运营社群活动
  • 玉米病虫害数据集检测识别数据集:近4k图像,7类,yolo标注
  • Batch Size与预热导致深度学习模型推理时间忽快忽慢
  • 过滤器(Filter)与拦截器(Interceptor)知识点总结
  • 深度学习与机器学习
  • Linux服务器从零开始-mysql安装
  • Emacs 折腾日记(三十)——打造C++ IDE 续
  • 解密DNS:互联网的隐形导航系统
  • Mysql修改用户密码,修改MySQL密码如何安全的步骤是什么
  • PS练习2:将图片贴入实际环境中
  • cocos shader 流光环绕
  • kali nethunter 开启ssh
  • vue3滚动到顶部钩子函数+组件简单示例
  • Linux 开发工具(3)
  • Hive 运行