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