前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >模式物语之装饰器

模式物语之装饰器

作者头像
LA0WAN9
发布2021-12-14 08:09:47
2860
发布2021-12-14 08:09:47
举报
文章被收录于专栏:火丁笔记

所谓装饰器,英文称之为Decorator,亦或者Wrapper。如果让我选择最喜爱的模式,我想我会毫不犹豫的投它一票。那到底什么是装饰器呢?且听我慢慢道来。

热身问题:顾客来买车,车本身有一个价格,如果要加内饰、镀膜、导航等配件,就需要另加钱。等到结帐的时候,程序应该如何处理呢?最直接的方法就是创建车类,和内饰、镀膜、导航等配件类,然后根据顾客的购买情况遍历获取最终价格。这样做最大的问题是对象的概念被割裂了,计算价格的操作变得过程化。当然我们可以通过创建子类来解决这类问题,比如根据不同的配件组合创建不同的车子类,如:内饰车类,镀膜车类,导航车类,但这还不算完,还有内饰镀膜车类,内饰导航车类等等。如果新加一个配件,还会衍生出若干个新的子类。早晚有一天你会崩溃的。

装饰器可以解决此类问题,其UML如下图所示:

装饰器

对应到我们的热身问题,车就是ConcreteComponent,至于内饰、镀膜、导航等配件都是ConcreteDecorator,根据顾客的购买情况,我们可以用一个或多个配件来装饰车,看上去就好像嵌套实例化一样,在实际调用operation计算价格的时候,每个装饰器都有机会修正结果,从而实现动态扩展对象的目的。

不过车的问题似乎离我们这些苦逼程序员有点远,所以就不编码实现了。我还是列举一些大家都耳熟能详的问题吧:我们做了一个后台,用户在操作的时候,我们需要判断用户身份,以及是否有权限等等,类似的逻辑很多,如何设计才能保证可扩展性?

下面,我会利用装饰器模式来解决这个问题,实现一个可扩展的控制器:

首先我们创建一个抽象的Action类,并通过继承它创建一个具体的AdminAction类,并配置好它的Decorators属性(用属性来消灭配置文件):

代码语言:javascript
复制
<?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类,需要注意的是装饰器本身也可以被装饰,但这有可能会造成递归死循环,本文出于篇幅的考虑忽略了此问题:

代码语言:javascript
复制
<?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,它就像一个前端控制器一样:

代码语言:javascript
复制
<?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();
    }
}

?>

大功告成,我们可以运行一下代码看看效果:

代码语言:javascript
复制
<?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等方法来实现装饰模式,但那样显得太生硬了,我不喜欢。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2012-03-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档