1 核心定义
装饰模式是一种结构型设计模式,它允许在不改变现有对象结构的情况下,动态地给该对象添加一些额外的职责。装饰模式通过创建包装对象(装饰器)来包裹真实对象,提供了比继承更有弹性的替代方案。
2 核心思想
继承的替代方案:提供比继承更灵活的扩展方式
开闭原则:对扩展开放,对修改封闭
动态性:可以在运行时动态添加或撤销功能
透明性:装饰对象和真实对象具有相同的接口
3 结构组成
Component(组件接口):定义对象接口,可动态添加职责
ConcreteComponent(具体组件):实现组件接口的具体对象
Decorator(装饰器抽象类):持有一个组件引用,实现组件接口
ConcreteDecorator(具体装饰器):向组件添加具体职责
4 应用场景举例
以咖啡订单系统为例,假设我们需要计算不同咖啡饮品的价格和描述:
基础咖啡:浓缩咖啡、美式咖啡
配料:牛奶、摩卡、奶油等
顾客可以自由组合基础咖啡和配料
5 UML

6 c++ 代码实现
cpp
#include <iostream>
#include <string>
using namespace std;
// 抽象组件:咖啡接口
class Coffee {
public:
virtual ~Coffee() = default;
virtual string getDescription() const = 0;
virtual double cost() const = 0;
};
// 具体组件:浓缩咖啡
class Espresso : public Coffee {
public:
string getDescription() const override {
return "浓缩咖啡";
}
double cost() const override {
return 25.0; // 基础价格25元
}
};
// 具体组件:美式咖啡
class Americano : public Coffee {
public:
string getDescription() const override {
return "美式咖啡";
}
double cost() const override {
return 20.0; // 基础价格20元
}
};
// 抽象装饰器:配料装饰器
class CoffeeDecorator : public Coffee {
protected:
Coffee* coffee; // 持有的被装饰对象
public:
CoffeeDecorator(Coffee* c) : coffee(c) {}
string getDescription() const override {
return coffee->getDescription();
}
double cost() const override {
return coffee->cost();
}
virtual ~CoffeeDecorator() {
delete coffee;
}
};
// 具体装饰器:牛奶
class Milk : public CoffeeDecorator {
public:
Milk(Coffee* c) : CoffeeDecorator(c) {}
string getDescription() const override {
return coffee->getDescription() + " + 牛奶";
}
double cost() const override {
return coffee->cost() + 5.0; // 牛奶加5元
}
};
// 具体装饰器:摩卡
class Mocha : public CoffeeDecorator {
public:
Mocha(Coffee* c) : CoffeeDecorator(c) {}
string getDescription() const override {
return coffee->getDescription() + " + 摩卡";
}
double cost() const override {
return coffee->cost() + 8.0; // 摩卡加8元
}
};
// 具体装饰器:奶油
class Whip : public CoffeeDecorator {
public:
Whip(Coffee* c) : CoffeeDecorator(c) {}
string getDescription() const override {
return coffee->getDescription() + " + 奶油";
}
double cost() const override {
return coffee->cost() + 6.0; // 奶油加6元
}
};
// 客户端代码
int main() {
// 点一杯浓缩咖啡加牛奶和摩卡
Coffee* coffee = new Espresso();
coffee = new Milk(coffee);
coffee = new Mocha(coffee);
cout << "订单: " << coffee->getDescription() << endl;
cout << "价格: " << coffee->cost() << "元" << endl;
delete coffee;
return 0;
}
7 总结
如果不使用装饰模式,可能会有以下几种实现方式
方式1:使用继承(每个组合创建一个子类)
cpp
// 问题:类爆炸
class EspressoWithMilk : public Espresso { /* ... */ };
class EspressoWithMocha : public Espresso { /* ... */ };
class EspressoWithMilkAndMocha : public Espresso { /* ... */ };
class AmericanoWithMilk : public Americano { /* ... */ };
// ... 需要为每种组合创建类,如果有3种基础咖啡和5种配料,就需要3*2^5=96个类!
方式2:使用属性标志
cpp
class Coffee {
private:
bool hasMilk;
bool hasMocha;
bool hasWhip;
// ... 需要为每种配料添加标志
public:
double cost() {
double total = basePrice;
if (hasMilk) total += 5;
if (hasMocha) total += 8;
if (hasWhip) total += 6;
return total;
}
};
主要问题:
1 违反开闭原则:每次添加新的配料都需要修改Coffee类
2 代码臃肿:类的职责过重,包含所有配料的逻辑
3 维护困难:添加新配料需要修改现有代码,容易引入bug
4 灵活性差:无法动态改变配料组合,配料的组合在编译时就固定了
5 代码重复:如果配料的价格计算逻辑复杂,不同咖啡类中会有重复代码
装饰模式的优势
1 灵活扩展:可以在不修改现有代码的情况下添加新功能
2 组合自由:可以在运行时自由组合不同的装饰器
3 符合单一职责:每个装饰器只负责一个特定的功能
4 避免类爆炸:不需要为每个组合创建新的子类
5 可撤销:可以动态地添加或移除装饰器
装饰模式通过将复杂的功能分解为简单的小功能,并通过组合的方式实现复杂功能,是一种非常灵活的代码组织方式。