Skip to content

装饰器模式(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 + Cream

2.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;
    }
}

七、全文总结

装饰器模式的核心是 动态地给对象添加额外功能,比继承更灵活

核心要点

  1. 装饰器和被装饰对象实现相同接口
  2. 装饰器持有被装饰对象引用,可链式组合
  3. 适用于功能可自由组合的场景
  4. PHP 8.0+ 提供属性(Attribute)支持装饰器语法
  5. 避免装饰链过长,可结合建造者模式优化