模式物语之装饰器

所谓装饰器,英文称之为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等方法来实现装饰模式,但那样显得太生硬了,我不喜欢。

模式物语之装饰器》上有10个想法

  1. 这个Action的例子其实算是代理模式了吧。不过模式这种东西本来就没什么规范标准,怎么合适怎么用

  2. ”结尾再唠叨一点题外话,Python对装饰器提供了语法级的实现(PEP0308/3129),虽然对我们LAMP程序员来说,这只有羡慕嫉妒恨的份儿,但多了解了解总比坐井观天强“

    python的decorator用得蛮舒服,还不知道PHP没提供decorator语法级别支持呢,不过python开发程序员很多也是LAMP程序员哦,只不过PHP改成PYTHON

  3. Pingback引用通告: Web框架与太阳系 | 火丁笔记

  4. Pingback引用通告: Web框架与太阳系

  5. Pingback引用通告: Web框架与太阳系 - IT牛人博客聚合

  6. Pingback引用通告: Web框架与太阳系-SuperMan's blog

发表评论

电子邮件地址不会被公开。 必填项已用*标注