装饰器模式(Decorator)是结构型设计模式的一种,它通过动态地给对象添加额外功能,同时不改变其原有的结构和接口。这种模式相比继承更灵活,能够在运行时灵活组合不同功能,避免了类爆炸问题。
一、核心思想与角色
装饰器模式的核心是"功能扩展与原对象解耦",通过包裹(wrap)原对象实现功能叠加。其核心角色如下:
角色名称 | 核心职责 |
---|---|
抽象组件(Component) | 定义被装饰对象和装饰器的共同接口,是所有组件的抽象基类。 |
具体组件(ConcreteComponent) | 实现抽象组件接口,是被装饰的原始对象(基础功能)。 |
抽象装饰器(Decorator) | 继承抽象组件,内部包含一个抽象组件的引用,实现与抽象组件一致的接口。 |
具体装饰器(ConcreteDecorator) | 继承抽象装饰器,添加具体的额外功能,在调用原组件方法前后执行附加逻辑。 |
核心思想:用"组合"代替"继承"实现功能扩展,每个装饰器专注于单一功能,通过嵌套组合实现多功能叠加,且扩展过程对客户端透明。
二、实现示例(咖啡订单系统)
假设我们需要设计一个咖啡订单系统,基础咖啡(如美式咖啡)可添加多种配料(牛奶、糖、巧克力等),每种配料会增加价格和描述。使用装饰器模式可灵活组合配料:
cpp
#include <iostream>
#include <string>
// 1. 抽象组件(Component):咖啡
class Coffee {
public:
virtual std::string getDescription() const = 0; // 获取描述
virtual double cost() const = 0; // 获取价格
virtual ~Coffee() = default;
};
// 2. 具体组件(ConcreteComponent):基础咖啡(美式咖啡)
class Espresso : public Coffee {
public:
std::string getDescription() const override {
return "美式咖啡";
}
double cost() const override {
return 25.0; // 基础价格
}
};
// 3. 抽象装饰器(Decorator):配料装饰器
class CoffeeDecorator : public Coffee {
protected:
Coffee* coffee; // 被装饰的咖啡对象(组合关系)
public:
// 构造函数:接收被装饰的咖啡
CoffeeDecorator(Coffee* c) : coffee(c) {}
// 析构函数:释放被装饰的对象(若为装饰器链,需注意释放顺序)
~CoffeeDecorator() override {
delete coffee;
}
};
// 4. 具体装饰器1:牛奶
class Milk : public CoffeeDecorator {
public:
Milk(Coffee* c) : CoffeeDecorator(c) {}
// 扩展描述:添加"加牛奶"
std::string getDescription() const override {
return coffee->getDescription() + ",加牛奶";
}
// 扩展价格:+5元
double cost() const override {
return coffee->cost() + 5.0;
}
};
// 4. 具体装饰器2:糖
class Sugar : public CoffeeDecorator {
public:
Sugar(Coffee* c) : CoffeeDecorator(c) {}
std::string getDescription() const override {
return coffee->getDescription() + ",加糖";
}
double cost() const override {
return coffee->cost() + 2.0;
}
};
// 4. 具体装饰器3:巧克力
class Chocolate : public CoffeeDecorator {
public:
Chocolate(Coffee* c) : CoffeeDecorator(c) {}
std::string getDescription() const override {
return coffee->getDescription() + ",加巧克力";
}
double cost() const override {
return coffee->cost() + 8.0;
}
};
// 客户端代码:组合不同配料
int main() {
// 1. 基础咖啡(无配料)
Coffee* espresso = new Espresso();
std::cout << "订单1:" << espresso->getDescription()
<< ",价格:" << espresso->cost() << "元" << std::endl;
delete espresso;
// 2. 美式咖啡 + 牛奶
Coffee* milkCoffee = new Milk(new Espresso());
std::cout << "订单2:" << milkCoffee->getDescription()
<< ",价格:" << milkCoffee->cost() << "元" << std::endl;
delete milkCoffee;
// 3. 美式咖啡 + 牛奶 + 糖 + 巧克力(多层装饰)
Coffee* deluxeCoffee = new Chocolate(
new Sugar(
new Milk(
new Espresso()
)
)
);
std::cout << "订单3:" << deluxeCoffee->getDescription()
<< ",价格:" << deluxeCoffee->cost() << "元" << std::endl;
delete deluxeCoffee;
return 0;
}
三、代码解析
-
抽象组件(Coffee) :定义了所有咖啡(包括基础咖啡和装饰后的咖啡)的通用接口(
getDescription()
和cost()
)。 -
具体组件(Espresso):实现了基础咖啡的功能,提供默认描述(美式咖啡)和价格(25元),是被装饰的原始对象。
-
抽象装饰器(CoffeeDecorator) :继承
Coffee
接口,内部持有一个Coffee*
指针(被装饰对象),实现了装饰器的基础结构。其析构函数负责释放被装饰对象,避免内存泄漏。 -
具体装饰器:
- 每个装饰器(
Milk
、Sugar
等)专注于单一功能(添加某种配料)。 - 在
getDescription()
中扩展原咖啡的描述,在cost()
中增加配料价格,同时通过coffee
指针调用原对象的方法,实现"功能叠加"。
- 每个装饰器(
-
客户端使用 :通过嵌套构造函数组合不同装饰器(如
Chocolate(Sugar(Milk(Espresso())))
),动态创建具有多种功能的对象,且无需修改原有类。
四、核心优势与适用场景
优势
- 灵活扩展:可在运行时动态添加/移除功能,比继承更灵活(继承是编译时静态扩展)。
- 单一职责:每个装饰器只负责一种功能,符合单一职责原则,便于维护。
- 避免类爆炸 :若用继承实现"基础咖啡+3种配料"的组合,需
2^3-1=7
个类,而装饰器模式只需3个装饰器类,组合更灵活。 - 透明扩展:客户端使用装饰后的对象与使用原始对象完全一致(接口相同),无需区分。
适用场景
- 需动态为对象添加/移除功能(如动态添加日志、缓存、权限校验等)。
- 功能可组合且组合方式多样(如咖啡配料、UI组件的多层样式)。
- 不适合用继承扩展(如类层次过深、功能组合过多)。
五、与其他模式的区别
模式 | 核心差异点 |
---|---|
装饰器模式 | 动态添加功能,不改变接口,装饰器与被装饰者实现同一接口,强调"功能扩展"。 |
适配器模式 | 转换接口使不兼容的类协作,强调"接口适配",不关注功能扩展。 |
代理模式 | 控制对对象的访问(如延迟加载、权限控制),不添加新功能,强调"访问控制"。 |
组合模式 | 构建树形结构表示"部分-整体"关系,统一单个对象和组合对象的使用。 |
六、实践建议
- 保持接口一致:装饰器必须与被装饰对象实现相同的接口,确保客户端透明使用。
- 最小职责原则:每个装饰器只实现一个额外功能,避免设计复杂的"万能装饰器"。
- 注意内存管理:装饰器链的释放需谨慎(如示例中装饰器析构函数自动释放被装饰对象,避免二次删除)。
- 避免过度装饰:过多的装饰器嵌套可能导致代码可读性下降,必要时可封装常用组合为便捷方法。
装饰器模式的核心价值在于"在不改变原有对象的前提下,动态、灵活地扩展功能"。它通过组合而非继承的方式,解决了功能扩展导致的类爆炸问题,是实现"开放-封闭原则"的经典方案,特别适合需要灵活组合多种功能的场景。