装饰器模式(Decorator):逐层"贴能力",而不是改本尊
更新时间:2026-08-25。本文按"先看不用模式的困境 → 再用模式重构 → 类图对比分析"的结构,论述装饰器模式。可运行代码:demos/design-patterns/decorator。
一、问题场景:给对象动态叠加可选能力
咖啡加糖/加奶/加巧克力、数据流加缓冲/加密/压缩、窗口加滚动条/边框/阴影。核心对象稳定,附加能力按需组合,且组合方式成千上万(2^n 种叠加)。
二、不使用模式:继承组合爆炸 / 直接改原类
方案 A:继承逐层派生
cpp
class Coffee {};
class SugarCoffee : public Coffee {};
class MilkCoffee : public Coffee {};
class SugarMilkCoffee : public Coffee {}; // 2^3=8 种组合,n 种能力 2^n 爆炸| 缺陷 | 说明 |
|---|---|
| 组合爆炸 | n 种能力 → 2^n 个子类 |
| 违反 OCP | 加一种配料要新增一半组合类 |
| 运行时不灵活 | 组合在编译期写死,无法按订单动态配 |
方案 B:给原类加开关字段
cpp
class Coffee { bool sugar_, milk_, choc_; double price(); };| 缺陷 | 说明 |
|---|---|
| 违反 SRP/OCP | 核心类被附加功能塞满,加能力要改它 |
| 能力与核心耦合 | 核心类的每次改动都要回归所有能力 |
不使用模式的类图

三、使用模式:装饰器包装核心,逐层叠加
装饰器模式让装饰器实现与核心相同的接口,并持有核心(或下层装饰器)引用;调用时先做自己的增强,再委托给被包装者——能力像洋葱一样逐层叠加,组合在运行时自由装配。
cpp
// coffee_decorator.h
class Coffee { // 核心接口
public:
virtual double price() const = 0;
virtual std::string desc() const = 0;
virtual ~Coffee() = default;
};
class PlainCoffee : public Coffee {
public:
double price() const override { return 10; }
std::string desc() const override { return "coffee"; }
};
class CoffeeDecorator : public Coffee { // 装饰器基类:转发
protected:
explicit CoffeeDecorator(Coffee& inner) : inner_(inner) {}
Coffee& inner_;
};
class Sugar : public CoffeeDecorator { // 具体装饰器:增强 + 委托
public:
explicit Sugar(Coffee& inner) : CoffeeDecorator(inner) {}
double price() const override { return inner_.price() + 2; }
std::string desc() const override { return inner_.desc() + " +sugar"; }
};
class Milk : public CoffeeDecorator {
public:
explicit Milk(Coffee& inner) : CoffeeDecorator(inner) {}
double price() const override { return inner_.price() + 3; }
std::string desc() const override { return inner_.desc() + " +milk"; }
};
// 运行时任意叠加
PlainCoffee base;
Sugar s(base); Milk m(s); // 10 + 2 + 3 = 15
std::cout << m.desc() << " " << m.price(); // "coffee +sugar +milk 15"使用模式的类图

四、类图对比与分析
| 维度 | 不使用(继承爆炸/开关字段) | 使用装饰器 |
|---|---|---|
| 类数量 | 2^n(组合子类) | n 个装饰器 + 1 核心 |
| 加新能力 | 组合子类翻倍 | 新增 1 个装饰器类 |
| 运行时组合 | 编译期写死 | 运行时逐层包装 |
| 核心类 | 被开关字段污染 / 被迫改 | 稳定不动(SRP) |
| 调用链 | 直接调用 | 逐层委托(多一次间接) |
分析结论:装饰器把"能力"变成可插拔的包装层,组合数量从 2^n 个子类降为 n 个装饰器。代价是调用链变长(每层一次虚调用)、调试栈变深。性能热点路径上要评估叠加层数;本仓库的性能实验中,装饰器链是"间接调用成本"的典型教学场景。
五、适用边界
| 适用 | 不适用 |
|---|---|
| 能力可按需组合、数量多(2^n 场景) | 能力固定不变(直接用继承) |
| 需要运行时动态叠加/剥离 | 叠加层数过多导致热点路径性能受损 |
| 核心类应保持稳定(SRP) | 能力需要访问核心私有内部(考虑访问者) |
与代理模式的区别:装饰器增强接口行为(同接口、加能力、可多层);代理控制接口访问(同接口、管权限/延迟/日志、通常单层)。装饰器从外部添加,代理从外部拦截。
一句话总结
装饰器模式用"同接口包装 + 逐层委托"把能力变成可叠加的洋葱层,组合从 2^n 个子类降为 n 个装饰器——核心类保持稳定、能力在运行时自由装配;代价是调用链加深,叠加层数在热点路径上要克制。