本文目录导读:

装饰器模式(Decorator Pattern)允许你动态地给一个对象添加新的功能,同时不改变其原有的结构,它通过创建一个包装对象(装饰器)来包裹原始对象。
下面我通过一个咖啡点单系统的例子来展示装饰器模式的核心用法。
核心组件(接口和基础类)
定义一个所有咖啡都必须实现的接口,以及一个基础的咖啡类。
<?php
// 1. 组件接口:定义了所有咖啡的基本操作
interface Coffee
{
public function getDescription(): string;
public function getCost(): float;
}
// 2. 具体组件:基础的浓缩咖啡(被装饰的对象)
class Espresso implements Coffee
{
public function getDescription(): string
{
return '浓缩咖啡';
}
public function getCost(): float
{
return 20.00;
}
}
// 另一个基础组件:美式咖啡
class Americano implements Coffee
{
public function getDescription(): string
{
return '美式咖啡';
}
public function getCost(): float
{
return 25.00;
}
}
抽象装饰器(基类)
装饰器基类实现 Coffee 接口,并持有一个 Coffee 对象的引用,它的作用是将请求转发给被包裹的对象。
<?php
// 3. 抽象装饰器:所有调料(装饰器)的基类
abstract class CoffeeDecorator implements Coffee
{
protected Coffee $coffee;
public function __construct(Coffee $coffee)
{
$this->coffee = $coffee;
}
// 默认实现:转发给被包裹的咖啡
public function getDescription(): string
{
return $this->coffee->getDescription();
}
public function getCost(): float
{
return $this->coffee->getCost();
}
}
具体装饰器(各种调料)
每个具体装饰器都继承自 CoffeeDecorator,并重写父类的方法,在原有功能上增加新的描述和价格。
<?php
// 4. 具体装饰器A:牛奶
class Milk extends CoffeeDecorator
{
public function getDescription(): string
{
return $this->coffee->getDescription() . ',加牛奶';
}
public function getCost(): float
{
return $this->coffee->getCost() + 5.00;
}
}
// 具体装饰器B:糖
class Sugar extends CoffeeDecorator
{
public function getDescription(): string
{
return $this->coffee->getDescription() . ',加糖';
}
public function getCost(): float
{
return $this->coffee->getCost() + 2.00;
}
}
// 具体装饰器C:奶泡(拿铁风格)
class Whip extends CoffeeDecorator
{
public function getDescription(): string
{
return $this->coffee->getDescription() . ',加奶泡';
}
public function getCost(): float
{
return $this->coffee->getCost() + 3.00;
}
}
客户端使用(动态组合)
这是装饰器模式最核心的用法:运行时自由组合装饰器,而无需修改现有类。
<?php // 引入所有文件(此处假设已自动加载) // 1. 点一杯基础的浓缩咖啡 $coffee = new Espresso(); echo $coffee->getDescription() . ':' . $coffee->getCost() . '元<br>'; // 输出:浓缩咖啡:20元 // 2. 动态地加牛奶和糖 $coffeeWithMilk = new Milk($coffee); $coffeeWithMilkAndSugar = new Sugar($coffeeWithMilk); echo $coffeeWithMilkAndSugar->getDescription() . ':' . $coffeeWithMilkAndSugar->getCost() . '元<br>'; // 输出:浓缩咖啡,加牛奶,加糖:27元 // 3. 换一种点法:直接嵌套(先加糖,再加奶泡) $coffee2 = new Espresso(); $decorated = new Whip(new Sugar($coffee2)); echo $decorated->getDescription() . ':' . $decorated->getCost() . '元<br>'; // 输出:浓缩咖啡,加糖,加奶泡:25元 // 4. 装饰器也可以用于其他基础咖啡(美式) $americanoWithEverything = new Whip(new Sugar(new Milk(new Americano()))); echo $americanoWithEverything->getDescription() . ':' . $americanoWithEverything->getCost() . '元<br>'; // 输出:美式咖啡,加牛奶,加糖,加奶泡:35元
模式总结
装饰器模式的关键点:
- 动态性:装饰器可以在运行时添加,而不像继承那样在编译时固定。
- 组合优于继承:你可以通过任意顺序组合装饰器,产生多样化的行为(奶+糖、糖+奶泡、奶+糖+奶泡)。
- 透明性:装饰器实现了与被装饰对象相同的接口,客户端不需要知道它正在使用装饰器。
在PHP中的实际应用场景:
- 中间件(如 Laravel 中间件):一层层包裹请求处理逻辑。
- 日志系统:为文件日志添加时间戳、加密、压缩等功能。
- 前端控制器:为请求添加缓存、权限验证等。
对比继承:如果使用继承,你需要为“浓缩+牛奶”、“浓缩+糖”、“浓缩+牛奶+糖”、“美式+牛奶+糖”等分别创建子类,这会导致类爆炸,装饰器模式通过组合解决了这个问题。