装饰器模式(Decorator)
前言
装饰器模式允许在不改变对象自身的前提下,动态地给对象添加额外职责。它比继承更灵活,是替代继承扩展功能的优选方案。本文将详细讲解装饰器模式的核心原理、链式装饰技巧及实际应用。
一、核心概念
1.1 定义
动态地给一个对象添加一些额外的职责。就增加功能而言,装饰器模式比生成子类更加灵活。
1.2 核心角色
| 角色 | 说明 |
|---|---|
| 组件接口(Component) | 定义对象接口 |
| 具体组件(ConcreteComponent) | 被装饰的原始对象 |
| 装饰器基类(Decorator) | 实现组件接口,持有组件引用 |
| 具体装饰器(ConcreteDecorator) | 添加具体功能 |
1.3 与继承的区别
| 对比项 | 继承 | 装饰器 |
|---|---|---|
| 扩展方式 | 静态(编译时) | 动态(运行时) |
| 灵活性 | 低,类爆炸 | 高,可任意组合 |
| 修改原类 | 是 | 否 |
| 多功能组合 | 类数量指数增长 | 灵活组合 |
二、代码实现
2.1 基础实现
php
<?php
// 组件接口
interface Component
{
public function operation(): string;
}
// 具体组件
class ConcreteComponent implements Component
{
public function operation(): string
{
return "ConcreteComponent";
}
}
// 装饰器基类
abstract class Decorator implements Component
{
protected Component $component;
public function __construct(Component $component)
{
$this->component = $component;
}
public function operation(): string
{
return $this->component->operation();
}
}
// 具体装饰器 A:添加前缀
class ConcreteDecoratorA extends Decorator
{
public function operation(): string
{
return "DecoratorA(" . parent::operation() . ")";
}
}
// 具体装饰器 B:添加后缀
class ConcreteDecoratorB extends Decorator
{
public function operation(): string
{
return "DecoratorB(" . parent::operation() . ")";
}
}
// 使用:动态组合装饰器
$component = new ConcreteComponent();
echo $component->operation() . PHP_EOL;
// ConcreteComponent
$decoratedA = new ConcreteDecoratorA($component);
echo $decoratedA->operation() . PHP_EOL;
// DecoratorA(ConcreteComponent)
// 链式装饰:A 和 B 同时装饰
$decoratedAB = new ConcreteDecoratorB($decoratedA);
echo $decoratedAB->operation() . PHP_EOL;
// DecoratorB(DecoratorA(ConcreteComponent))2.2 实际案例:咖啡订单系统
php
<?php
// 咖啡接口
interface Coffee
{
public function cost(): float;
public function description(): string;
}
// 基础咖啡
class SimpleCoffee implements Coffee
{
public function cost(): float
{
return 10;
}
public function description(): string
{
return "Simple Coffee";
}
}
// 装饰器基类
abstract class CoffeeDecorator implements Coffee
{
protected Coffee $coffee;
public function __construct(Coffee $coffee)
{
$this->coffee = $coffee;
}
public function cost(): float
{
return $this->coffee->cost();
}
public function description(): string
{
return $this->coffee->description();
}
}
// 牛奶装饰器
class MilkDecorator extends CoffeeDecorator
{
public function cost(): float
{
return parent::cost() + 3;
}
public function description(): string
{
return parent::description() . " + Milk";
}
}
// 糖装饰器
class SugarDecorator extends CoffeeDecorator
{
public function cost(): float
{
return parent::cost() + 1;
}
public function description(): string
{
return parent::description() . " + Sugar";
}
}
// 奶油装饰器
class CreamDecorator extends CoffeeDecorator
{
public function cost(): float
{
return parent::cost() + 5;
}
public function description(): string
{
return parent::description() . " + Cream";
}
}
// 使用:自由组合
$coffee = new SimpleCoffee();
echo $coffee->cost() . ", " . $coffee->description() . PHP_EOL;
// 10, Simple Coffee
$milkCoffee = new MilkDecorator($coffee);
echo $milkCoffee->cost() . ", " . $milkCoffee->description() . PHP_EOL;
// 13, Simple Coffee + Milk
// 牛奶 + 糖 + 奶油
$fullCoffee = new CreamDecorator(
new SugarDecorator(new MilkDecorator(new SimpleCoffee()))
);
echo $fullCoffee->cost() . ", " . $fullCoffee->description() . PHP_EOL;
// 19, Simple Coffee + Milk + Sugar + Cream2.3 PHP 属性装饰器(PHP 8.0+)
php
<?php
// 日志装饰器属性
#[\Attribute(\Attribute::TARGET_METHOD)]
class Log
{
public function __construct(public string $level = 'info') {}
}
// 性能装饰器属性
#[\Attribute(\Attribute::TARGET_METHOD)]
class Measure {}
class Calculator
{
#[Log(level: 'debug')]
#[Measure]
public function add(int $a, int $b): int
{
return $a + $b;
}
}
// 通过反射读取并应用装饰逻辑
$ref = new ReflectionMethod(Calculator::class, 'add');
foreach ($ref->getAttributes() as $attr) {
echo "Attribute: " . $attr->getName() . PHP_EOL;
}三、适用场景
| 场景 | 说明 |
|---|---|
| 动态添加功能 | 运行时决定功能组合 |
| 功能可组合 | 多种功能自由搭配 |
| 避免继承爆炸 | 替代多层级继承 |
| 日志/缓存/权限 | 横切关注点处理 |
| UI 组件增强 | 组件功能叠加 |
四、优缺点分析
| 优点 | 缺点 |
|---|---|
| 动态添加功能 | 可能产生大量小类 |
| 比继承更灵活 | 装饰链过长影响性能 |
| 符合开闭原则 | 调试困难,调用栈深 |
| 功能可自由组合 | 初始化顺序需注意 |
| 装饰可撤销 | 客户端需管理装饰器 |
五、常见踩坑与问题排查
5.1 装饰顺序错误
php
<?php
// 顺序不同,结果不同
$coffee1 = new SugarDecorator(new MilkDecorator(new SimpleCoffee()));
$coffee2 = new MilkDecorator(new SugarDecorator(new SimpleCoffee()));
// 如果装饰器有副作用(如修改基础对象),顺序可能影响结果5.2 装饰器不知道被装饰对象的具体类型
php
<?php
// 问题:装饰后无法访问原始对象特有方法
$decorated = new MilkDecorator(new SimpleCoffee());
// decorated 只能访问 Coffee 接口方法
// 解决:通过接口设计确保所有功能通过装饰器实现5.3 装饰链过长
php
<?php
// 问题:装饰链太长影响性能和可读性
$result = new D5(new D4(new D3(new D2(new D1(new ConcreteComponent())))));
// 解决:合并部分装饰器,或使用策略模式替代六、优化方案与进阶
6.1 装饰器建造者
php
<?php
class CoffeeBuilder
{
private Coffee $coffee;
public function __construct(Coffee $base)
{
$this->coffee = $base;
}
public function addMilk(): self
{
$this->coffee = new MilkDecorator($this->coffee);
return $this;
}
public function addSugar(): self
{
$this->coffee = new SugarDecorator($this->coffee);
return $this;
}
public function addCream(): self
{
$this->coffee = new CreamDecorator($this->coffee);
return $this;
}
public function build(): Coffee
{
return $this->coffee;
}
}
// 使用链式调用构建
$coffee = (new CoffeeBuilder(new SimpleCoffee()))
->addMilk()
->addSugar()
->build();6.2 结合代理模式
php
<?php
// 装饰器增强功能,代理控制访问
class CachingDecorator extends Decorator
{
private array $cache = [];
public function operation(string $key = 'default'): string
{
if (isset($this->cache[$key])) {
return $this->cache[$key];
}
$result = parent::operation();
$this->cache[$key] = $result;
return $result;
}
}七、全文总结
装饰器模式的核心是 动态地给对象添加额外功能,比继承更灵活。
核心要点:
- 装饰器和被装饰对象实现相同接口
- 装饰器持有被装饰对象引用,可链式组合
- 适用于功能可自由组合的场景
- PHP 8.0+ 提供属性(Attribute)支持装饰器语法
- 避免装饰链过长,可结合建造者模式优化
