装饰器顾名思义,就是在原有内容上“增添装饰品”,就像装修房子一样,先刷墙,再贴瓷砖,再安装柜子...
装饰器模式主要解决继承关系过于复杂的问题,通过组合来替代继承。它主要的作用是给原始类添加增强功能,这也是判断是否该用装饰器模式的一个重要的依据。除此之外,装饰器模式还有一个特点,那就是可以对原始类嵌套使用多个装饰器。为了满足这个应用场景,在设计的时候,装饰器类需要跟原始类继承相同的抽象类或者接口。装饰器类和原始类继承同样的父类,这样我们可以对原始类“嵌套”多个装饰器类。装饰器类是对功能的增强,这也是装饰器模式应用场景的一个重要特点。
下面先看看PHP中基于接口实现一个装饰器模式的案例。
<?php
/**
* 装饰器模式:基于接口实现
*/
interface AnyThing {
function exe();
}
/**
* 定义三个没有任何耦合关系的类
*/
class Moon implements AnyThing {
private $param;
public function __construct($param) {
$this->param = $param;
}
public function exe() {
echo "明月装饰了";
$this->param->exe();
}
}
class Dream implements AnyThing {
private $param;
public function __construct($param) {
$this->param = $param;
}
public function exe() {
echo "梦装饰了";
$this->param->exe();
}
}
class You implements AnyThing {
private $param;
public function __construct($param) {
$this->param = $param;
}
public function exe() {
echo "你";
}
}
//创建对象的时候,可以通过构造函数的不同次序,使这几个类互相调用,从而呈现不同的装饰结果
$data1 = new Moon(new Dream(new You(null)));
$data1->exe(); //明月装饰了梦装饰了你
echo PHP_EOL;
$data2 = new Dream(new Moon(new You(null)));
$data2->exe(); //梦装饰了明月装饰了你
echo PHP_EOL;
代码是不是很好理解,可以按照不同的顺序去装饰,会得到不同的效果。下面再基于继承的方式实现一个装饰器模式。
假设有这么一个业务场景,作者发布一篇文章之后,可能需要 编辑人员、SEO推广人员、广告部人员分别给这篇文章增加内容,但是顺序不确定,而且增加的次数也不确定,这个时候就可以使用装饰器模式来实现。代码如下:
<?php
/**
* 文章类
*/
class Article {
protected $content; //文章内容
protected $article = null; //文章对象
public function __construct($content) {
$this->content = $content;
}
public function decorator() {
return $this->content;
}
}
/**
* 编辑人员添加摘要,继承原始的文章类
*/
class Editor extends Article {
public function __construct(Article $article) {
$this->article = $article;
}
public function decorator() {
//调用父类的decorator方法,获取父类的content,然后再添加自己的装饰内容,下同
$this->content = $this->article->decorator() . '【新增摘要from Editor】';
return $this->content;
}
}
/**
* SEO人员添加推广信息
*/
class SEOer extends Article {
public function __construct(Article $article) {
$this->article = $article;
}
public function decorator() {
$res = $this->content = $this->article->decorator() . '【新增推广信息from SEOer】';
return $res;
}
}
/**
* 广告人员添加广告信息
*/
class ADer extends Article {
public function __construct(Article $article) {
$this->article = $article;
}
public function decorator() {
$res = $this->content = $this->article->decorator() . '【新增广告信息from ADer】';
return $res;
}
}
/**
* todo 根据需要,可以继续装饰其它内容...
*/
//客户端调用
$content = "这是一篇文章";
$article = new Article($content);
//直接发表文章,不装饰任何内容
echo $article->decorator() . PHP_EOL; //这是一篇文章
//Editor先装饰,SEOer再修饰
$client = new SEOer(new Editor($article));
echo $client->decorator() . PHP_EOL; //这是一篇文章【新增摘要from Editor】【新增推广信息from SEOer】
//SEOer先装饰,Editor再修饰,ADer最后装饰
$client = new ADer(new Editor(new SEOer($article)));
echo $client->decorator() . PHP_EOL; //这是一篇文章【新增推广信息from SEOer】【新增摘要from Editor】【新增广告信息from ADer】
//Editor修饰两遍
$client = new Editor(new Editor($article));
echo $client->decorator() . PHP_EOL; //这是一篇文章【新增摘要from Editor】【新增摘要from Editor】
其实只要理解了“装饰”的含义,就能在实际开发中联想到哪些场景可以使用装饰器模式。
源代码:https://gitee.com/rxbook/php_design_pattern/blob/master/code09_decorator1.php
https://gitee.com/rxbook/php_design_pattern/blob/master/code10_decorator2.php