php设计模式(十一):装饰器模式(Decorator)
- 陈大剩
- 2023-04-25 23:02:25
- 697
装饰器模式
装饰器模式又称:装饰者模式、Wrappe、Decorator。装饰是一种结构型设计模式,允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。
问题
装饰器模式就如生活中的装饰或者配料一样,一级一级包装。就拿长沙最火的 茶颜悦色 奶茶举例吧,茶颜悦色的奶茶有原味奶茶:幽兰拿铁(特色)、声声乌龙、三季生椰 等,大部分奶茶都可以通过加价进行加料,加料类型有:奶油、碧根果碎、开心果碎 等,现实生活中我们可以买一杯奶茶进行多次加料,也可不加料,甚至可以把同一种料加三次,在软件开发中我们能很简单通过继承实现。
装饰模式是为已有的类动态添加更多功能,而且不改动原来的类基础上,使用 关联替代继承。
解决方法
上述情况我们需要更改一个对象的行为时,第一个跳入脑海的想法就是扩展它所属的类。
但是,不能忽视继承可能引发的几个严重问题:
- 继承是静态的。 无法在运行时更改已有对象的行为, 只能 使用由不同子类创建的对象来替代当前的整个对象。
- 子类只能有一个父类。大部分编程语言不允许一个类同时继承多个类的行为。
前面我们介绍了 组合模式 和 适配器模式 , 都是利用了设计原则中 组合优于继承的意识,在装饰器模式中也不例外。我们可以将包含多个指向其他对象的引用, 并将各种工作 委派给引用对象。
结构
MilkTea:原本的对象和装饰共同的接口 示例中指:奶茶
Oolong、Latte: 原本的对象 示例中指:声声乌龙、幽兰拿铁
Decorator: 实现接口的装饰抽象类
Cream、…:具体的装饰 示例中:奶油、碧根果、开心果
代码示例
奶茶基类
/**
* 奶茶
*/
interface MilkTea
{
/**
* 名称
* @return mixed
*/
public function name();
/**
* 价格
* @return mixed
*/
public function price();
}
具体奶茶类
声声乌龙
/**
* 声声乌龙
*/
class Oolong implements MilkTea
{
public function name()
{
return '声声乌龙';
}
public function price()
{
return 16;
}
}
幽兰拿铁
/**
* 幽兰拿铁
*/
class Latte implements MilkTea
{
public function name()
{
return '幽兰拿铁';
}
public function price()
{
return 17;
}
}
加料类基类
class Decorator implements MilkTea
{
protected $milkTea;
public function __construct(MilkTea $milkTea)
{
$this->milkTea = $milkTea;
}
public function name()
{
if ($this->milkTea != null) {
return $this->milkTea->name();
}
return '';
}
public function price()
{
if ($this->milkTea != null) {
return $this->milkTea->price();
}
return 0;
}
}
加料具体类
奶油
/**
* 奶油
*/
class Cream extends Decorator
{
public function name()
{
return $this->milkTea->name() . '+ 奶油';
}
public function price()
{
return $this->milkTea->price() + 4;
}
}
碧根果
/**
* 碧根果
*/
class Pecan extends Decorator
{
public function name()
{
return $this->milkTea->name() . '+ 碧根果';
}
public function price()
{
return $this->milkTea->price() + 2;
}
}
开心果
/**
* 开心果
*/
class Pistachio extends Decorator
{
public function name()
{
return $this->milkTea->name() . '+ 开心果';
}
public function price()
{
return $this->milkTea->price() + 4;
}
}
客户端使用
/**
* 我点一杯 幽兰拿铁+ 奶油+ 开心果
*/
$latte = new Latte();
$cream = new Cream($latte);
$pistachio = new Pistachio($cream);
echo $pistachio->name();
echo ' ' . $pistachio->price() . '元' . PHP_EOL;
/**
* 点一杯加三个奶油的声声乌龙(因为我比较喜欢喝奶油)
*/
$oolong = new Oolong();
$cream = new Cream($oolong);
$cream = new Cream($cream);
$cream = new Cream($cream);
echo $cream->name();
echo ' ' . $cream->price() . '元' . PHP_EOL;
输出
幽兰拿铁+ 奶油+ 开心果 25元
声声乌龙+ 奶油+ 奶油+ 奶油 28元
UML
优缺点
优点
- 无需创建新子类即可扩展对象的行为。
- 可以在运行时添加或删除对象的功能。
- 可以用多个装饰封装对象来组合几种行为。
- 单一职责原则。可以将实现了许多不同行为的一个大类拆 分为多个较小的类。
缺点
- 在封装器栈中删除特定封装器比较困难。
- 实现行为不受装饰栈顺序影响的装饰比较困难。
- 各层的初始化配置代码看上去可能会很糟糕。
总结
学过 建造者模式 的朋友可能会有疑问,这个怎么不直接用 建造者模式,其实 建造者模式 强调的是按照顺序稳定执行,而 装饰器模式 则可以自由灵活组合,比如像前面例子,我可以一杯奶茶点三个料。