装饰器模式通过包裹原始对象来附加职责,在日志、缓存、权限校验等横切场景中非常实用。但它并非万能,其使用受到场景边界和类型系统的双重约束。

装饰器模式的适用边界
装饰器模式最适合满足以下条件的场景:
- 被装饰对象的行为通过接口或抽象类稳定定义
- 扩展功能可以层层叠加且彼此独立
- 不希望使用继承导致子类膨胀
如果业务对象本身结构不稳定,或者需要扩展的是核心状态而非行为,装饰器就会显得笨重。例如直接修改数据模型的字段,用装饰器包裹反而隐藏了真实类型。
不适合使用装饰器的情形
| 情形 | 原因 |
|---|---|
| 接口频繁变更 | 每层装饰器都要同步调整,维护成本高 |
| 需要访问具体子类特有方法 | 装饰器通常只暴露抽象类型,丢失具体类型信息 |
| 性能极度敏感 | 多层包装会带来额外调用开销 |
类型兼容性约束
在静态类型语言中,装饰器必须实现与被装饰对象相同的接口,否则调用方无法透明替换。下面以 TypeScript 为例说明类型约束:
// 定义组件接口
interface Component {
operate(): string;
}
// 具体组件
class ConcreteComponent implements Component {
operate(): string {
return '基础操作';
}
}
// 装饰器基类,必须实现相同接口
class Decorator implements Component {
constructor(protected component: Component) {}
operate(): string {
return this.component.operate();
}
}
// 具体装饰器
class LogDecorator extends Decorator {
operate(): string {
const result = this.component.operate();
return '日志:' + result;
}
}
// 使用
const c: Component = new LogDecorator(new ConcreteComponent());
console.log(c.operate());
上述代码中,LogDecorator 之所以能替换 ConcreteComponent,是因为二者都满足 Component 接口。若装饰器返回了 narrower 类型或增加了可选参数,调用方便可能出现类型错误。
动态类型语言中的差异
在 Python 这类动态类型语言中,类型兼容性依靠鸭子类型维持,装饰器即便不显式继承也可运行。但代价是 IDE 无法推断真实类型,重构时容易遗漏方法。
class Component:
def operate(self):
return '基础操作'
class Decorator:
def __init__(self, comp):
self.comp = comp
def operate(self):
return '日志:' + self.comp.operate()
c = Decorator(Component())
print(c.operate())
总结建议
使用装饰器前,先确认扩展是否围绕稳定接口的行为展开。在静态类型项目中,务必保证装饰器与被装饰者类型兼容;在动态语言中,也应通过约定或协议明确接口,降低隐性成本。