【读书笔记】《C++ Software Design》第九章:The Decorator Design Pattern
《C++ Software Design》第九章:The Decorator Design Pattern
在现实系统中,我们常常需要在不修改已有类的前提下,为对象添加新功能——比如增加日志记录、性能度量、缓存行为等等。这种按需叠加式功能扩展是软件工程中典型的需求。
Decorator(装饰器)模式正是为此而生的一种经典结构型设计模式。它提供了一种透明、组合式的方式来增强对象的行为,是继承替代方案中最灵活的一种。
Guideline 35:使用装饰器层次化地扩展功能
可能遇到的问题
假设你负责一个 DataSource
类,有人需要在读取数据时加密,另一个同事希望加上缓存,还有人需要日志跟踪。传统继承方式无法灵活组合这些需求:
class EncryptedDataSource : public DataSource {};
class CachedDataSource : public DataSource {};
class LoggingDataSource : public DataSource {};
那如果有人想要“加密+缓存+日志”呢?就不得不写出一系列组合子类:EncryptedCachedDataSourceWithLogging
……代码膨胀不可避免。
Decorator 模式解释
Decorator 模式通过组合而不是继承,将增强行为附加到已有对象上,并允许以层级结构形式叠加功能。
核心结构:
class DataSource {
public:virtual std::string read() = 0;virtual ~DataSource() = default;
};class FileDataSource : public DataSource {
public:std::string read() override {return "file_data";}
};class DataSourceDecorator : public DataSource {
protected:std::shared_ptr<DataSource> wrappee;
public:DataSourceDecorator(std::shared_ptr<DataSource> src) : wrappee(std::move(src)) {}
};class LoggingDecorator : public DataSourceDecorator {
public:using DataSourceDecorator::DataSourceDecorator;std::string read() override {std::cout << "[LOG] Reading data\n";return wrappee->read();}
};class CachingDecorator : public DataSourceDecorator {std::string cachedData;
public:using DataSourceDecorator::DataSourceDecorator;std::string read() override {if (cachedData.empty()) {cachedData = wrappee->read();}return cachedData;}
};
使用方式:
auto src = std::make_shared<FileDataSource>();
auto cached = std::make_shared<CachingDecorator>(src);
auto logged = std::make_shared<LoggingDecorator>(cached);std::string data = logged->read();
你可以任意组合装饰器而不改变底层类。
第二个例子:装饰图形对象
class Shape {
public:virtual void draw() const = 0;
};class Circle : public Shape {
public:void draw() const override {std::cout << "Drawing Circle\n";}
};class ColorDecorator : public Shape {std::shared_ptr<Shape> shape;std::string color;
public:ColorDecorator(std::shared_ptr<Shape> s, std::string c): shape(std::move(s)), color(std::move(c)) {}void draw() const override {shape->draw();std::cout << "With color: " << color << "\n";}
};
Decorator vs Adapter vs Strategy
模式 | 作用 | 是否替代接口 | 是否组合行为 | 使用粒度 |
---|---|---|---|---|
Decorator | 添加可选功能 | 否 | 是 | 实例 |
Adapter | 转换接口 | 是 | 否 | 类/接口 |
Strategy | 替换算法 | 是 | 否 | 类 |
Decorator 最大的特点是“功能增强叠加而非行为切换”。
Decorator 的短板分析
- 对于每个功能点都需一个子类,复杂度线性增长
- 不适合替换核心行为,只适合“附加行为”
- 调试时可能形成长链,调试栈难以定位
- 默认实现为运行时结构,可能存在虚函数调用开销
Guideline 36:理解运行时与编译时抽象的权衡
C++ 作为静态类型语言,其类型系统可在编译期执行许多抽象操作。Decorator 模式既可在运行时组合(如上所示),也可借助模板系统在编译期静态组合,从而实现零开销的增强。
编译时 Decorator:值语义的组合
通过模板类嵌套实现组合行为:
template <typename Base>
class LoggingDecorator {
public:Base base;void run() {std::cout << "[LOG] Before\n";base.run();std::cout << "[LOG] After\n";}
};class Task {
public:void run() {std::cout << "Running task\n";}
};
使用:
LoggingDecorator<Task> task;
task.run();
特点:
- 零运行时开销(无虚函数)
- 组合发生在编译期
- 更易内联优化
运行时 Decorator:基于值语义封装
可用 std::function 或类型擦除实现值类型的 runtime decorator:
std::function<void()> f = [] {std::cout << "Real task\n";
};std::function<void()> logWrapper = [f]() {std::cout << "[LOG] Start\n";f();std::cout << "[LOG] End\n";
};logWrapper();
适用于任务调度、管线执行等异步场景。
对比分析
特性 | 编译时 Decorator | 运行时 Decorator |
---|---|---|
性能 | 高(无虚调用) | 中等(虚调用或类型擦除) |
灵活性 | 差(必须编译期组合) | 高(运行时可决定组合) |
可维护性 | 中(模板膨胀) | 高(抽象清晰) |
类型侵入性 | 强(需暴露模板类型) | 弱(可封装任意对象) |
小结
Decorator 模式在 C++ 中拥有灵活实现方式:
- 作为运行时功能扩展机制,它是继承结构的有效替代
- 借助模板,可在编译期静态组合增强,获得更优性能
- 适合场景包括日志增强、缓存包装、动态功能组合、绘图增强等
搭配第八章的类型擦除技术,Decorator 模式还可以实现 非侵入式、运行时组合的功能链条,在现代微服务、中间件框架、事件驱动系统中发挥重要作用。
在高质量 C++ 软件架构设计中,Decorator 模式帮助我们延迟功能绑定、支持渐进式增强,避免陷入硬编码的继承层级。同时,它也体现了“组合优于继承”的软件设计哲学。