所谓装饰器,英文称之为Decorator,亦或者Wrapper。如果让我选择最喜爱的模式,我想我会毫不犹豫的投它一票。那到底什么是装饰器呢?且听我慢慢道来。
热身问题:顾客来买车,车本身有一个价格,如果要加内饰、镀膜、导航等配件,就需要另加钱。等到结帐的时候,程序应该如何处理呢?最直接的方法就是创建车类,和内饰、镀膜、导航等配件类,然后根据顾客的购买情况遍历获取最终价格。这样做最大的问题是对象的概念被割裂了,计算价格的操作变得过程化。当然我们可以通过创建子类来解决这类问题,比如根据不同的配件组合创建不同的车子类,如:内饰车类,镀膜车类,导航车类,但这还不算完,还有内饰镀膜车类,内饰导航车类等等。如果新加一个配件,还会衍生出若干个新的子类。早晚有一天你会崩溃的。
装饰器可以解决此类问题,其UML如下图所示:
装饰器
对应到我们的热身问题,车就是ConcreteComponent,至于内饰、镀膜、导航等配件都是ConcreteDecorator,根据顾客的购买情况,我们可以用一个或多个配件来装饰车,看上去就好像嵌套实例化一样,在实际调用operation计算价格的时候,每个装饰器都有机会修正结果,从而实现动态扩展对象的目的。
不过车的问题似乎离我们这些苦逼程序员有点远,所以就不编码实现了。我还是列举一些大家都耳熟能详的问题吧:我们做了一个后台,用户在操作的时候,我们需要判断用户身份,以及是否有权限等等,类似的逻辑很多,如何设计才能保证可扩展性?
下面,我会利用装饰器模式来解决这个问题,实现一个可扩展的控制器:
首先我们创建一个抽象的Action类,并通过继承它创建一个具体的AdminAction类,并配置好它的Decorators属性(用属性来消灭配置文件):
<?php
abstract class Action
{
public $decorators = array();
abstract public function execute();
}
class AdminAction extends Action
{
public $decorators = array(
'Auth'
);
public function execute()
{
var_dump('execute admin');
}
}
?>
接着我们创建一个抽象的Decorator类,并通过继承它创建一个具体的AuthDecortor类和UserDecorator类,需要注意的是装饰器本身也可以被装饰,但这有可能会造成递归死循环,本文出于篇幅的考虑忽略了此问题:
<?php
abstract class Decorator extends Action
{
protected $action;
public function __construct(Action $action)
{
$this->action = $action;
}
}
class AuthDecorator extends Decorator
{
public $decorators = array(
'User'
);
public function execute()
{
var_dump('begin auth');
$this->action->execute();
var_dump('end auth');
}
}
class UserDecorator extends Decorator
{
public function execute()
{
var_dump('begin user');
$this->action->execute();
var_dump('end user');
}
}
?>
最后我们创建一个Dispatcher,它就像一个前端控制器一样:
<?php
class Dispatcher
{
public static function run()
{
$factory = function($action) use(&$factory) {
$decorators = array_reverse($action->decorators);
foreach ($decorators as $decorator) {
$decorator .= 'Decorator';
$action = $factory(new $decorator($action));
}
return $action;
};
$action = $factory(new AdminAction());
$action->execute();
}
}
?>
大功告成,我们可以运行一下代码看看效果:
<?php
Dispatcher::run();
/*
begin user
begin auth
execute admin
end auth
end user
*/
?>
乍看上去,装饰器模式似乎和很多框架控制器中提供的before/after钩子方法的实现方式差不多,但实际上它们的运行机制完全不同,before/after能实现的效果,用装饰器都可以实现,但反过来却未必,比如:我可以实现一个事务装饰器,在装饰器里try/catch代码,一旦发现有未捕捉的异常就回滚,否则就提交,这个效果用before/after是无法实现的,因为try/catch是一个整体,不能割裂到before/after两个部分中去。
结尾再唠叨一点题外话,Python对装饰器提供了语法级的实现(PEP0308/3129),虽然对我们LAMP程序员来说,这只有羡慕嫉妒恨的份儿,但多了解了解总比坐井观天强。
另外贴一张来自Python社区的洋葱图片,生动的诠释了WEB请求的流程,同时也有助于大家深入理解装饰器模式的运行机制:洋葱核心是真正的业务逻辑,外面每层洋葱皮都是一个装饰器。我每次看它,都有一种醍醐灌顶的感觉:
透过洋葱看装饰器
补充:有人可能会问为什么我在例子中把控制器设计成单Action风格,而不是现在流行的多Action风格?这主要是因为只有使用单Action风格,接口才是稳定的(只有一个execute方法),如此一来才可以更优雅的使用装饰模式,当然如果是多Action的话,也可以使用魔术方法__call等方法来实现装饰模式,但那样显得太生硬了,我不喜欢。