装饰器(Decorator)模式

装饰器模式是结构型设计模式的一种,也叫包装器(Wrappe)模式。装饰器模式的定义为动态的给对象添加一些额外的职责。简单的说,如果你想在不改变现有功能的情况下,增加新的功能,这个时候我们就可以使用装饰器模式。

装饰器模式的结构

  1. 抽象构件角色(Component):定义一个抽象接口,以规范准备接收附加责任的对象。
  2. 具体构件角色(Concrete Component):这是被装饰者,定义一个将要被装饰增加功能的类。
  3. 装饰角色(Decorator):持有一个构件对象的实例,并定义了抽象构件定义的接口。
  4. 具体装饰角色(Concrete Decorator):负责给构件添加增加的功能。

示例

假设你正在开发一个提供通知功能的库, 其他程序可使用它向用户发送关于重要事件的通知。
库的最初版本基于 通知器Notifier类, 其中只有很少的几个成员变量, 一个构造函数和一个 send发送方法。 该方法可以接收来自客户端的消息参数, 并将该消息发送给一系列的邮箱, 邮箱列表则是通过构造函数传递给通知器的。 作为客户端的第三方程序仅会创建和配置通知器对象一次, 然后在有重要事件发生时对其进行调用。如下图:

过了一段时间后,随着用户的增多,你收到用户的反馈,用户希望除邮件通知外,增加新的通知,例如在重要的通知上发送短信通知,有的用户希望发送微信通知,或者qq通知。
这个也不难实现,我们需要扩展 通知器类, 然后在新的子类中加入额外的通知方法。 现在客户端要对所需通知形式的对应类进行初始化, 然后使用该类发送后续所有的通知消息。如下图:

很快就有人反馈了,为什么我们不能使用多种通知形式呢?如果事态紧急,用户应该会想在所有渠道中都收到相同的消息吧。
我们可以尝试创建一个特殊子类来将多种通知方法组合在一起以解决该问题。 但这种方式会使得代码量迅速膨胀, 不仅仅是程序库代码, 客户端代码也会如此。如图:

下面我们使用装饰器模式进行修改,代码如下:

具体构建角色 Notifiter.php

<?php
namespace DesignPatterns\Decorator;
/**
 * 具体构件角色concrete component
 * 这里我们不需要使用抽象component(抽象构件),所以只需要使用具体构件(concrete component)
 * 让装饰角色直接继承具体构件(concrete component),如果只需要一个具体装饰角色(concrete decorator),
 * 那么我们也可以将装饰角色(decorator)和具体装饰角色(concrete decorator)的责任合成一个类
 * Class Notifier
 * @package DesignPatterns\Decorator
 */
class Notifier
{
    public function send(){
        echo '发送邮件通知';
    }
}

装饰角色 BaseDecorator.php

<?php
namespace DesignPatterns\Decorator;
class BaseDecorator extends Notifier
{
    private $notifier;
    public function __construct(Notifier $notifier)
    {
        $this->notifier = $notifier;
    }
    public function send()
    {
        $this->notifier->send();
    }

}

具体装饰角色 SMSDecorator.php

<?php
namespace DesignPatterns\Decorator;
class SMSDecorator extends BaseDecorator
{
    public function send()
    {
        parent::send();
        echo ' 发送短信通知';
    }
}

具体装饰角色 QQDecorator.php

<?php

namespace DesignPatterns\Decorator;
class QQDecorator extends BaseDecorator
{
    public function send()
    {
        parent::send(); // TODO: Change the autogenerated stub
        echo ' 发送QQ通知';
    }
}

具体装饰角色 WeChatDecorator.php

<?php
namespace DesignPatterns\Decorator;
class WeChatDecorator extends BaseDecorator
{
    public function send()
    {
        parent::send(); // TODO: Change the autogenerated stub
        echo ' 发送微信通知';
    }
}

运行Client.php

<?php
namespace DesignPatterns\Decorator;
require __DIR__.'/../vendor/autoload.php';
class Client{
    public function run()
    {
        $notifier = new Notifier();
        $notifier->send();
        echo '<br>';
        $smsdecorator  = new SMSDecorator($notifier);
        $smsdecorator->send();
        echo '<br>';
        $qqdecorator = new QQDecorator($notifier);
        $qqdecorator->send();
        echo '<br>';
        $wechatdecorator = new WeChatDecorator($notifier);
        $wechatdecorator->send();
    }
}
$client  = new Client();
$client->run();

运行结果

总结

装饰模式,动态地给一个对象添加一些额外的职责,就增加功能来说,装饰模式比生成子类更为灵活。
装饰模式是为已有功能动态的添加更多功能的一种方式。

当系统需要新功能的时候,是向旧的类中添加新的代码。这些新的代码通常装饰了原有类的核心职责或主要行为。
在主类中加入新的字段,新的方法和新的逻辑,从而增加了主类的复杂度,而这些新加入的东西仅仅是为满足一些只在某种特定情况下才会执行的特殊行为的需要。装饰模式却提供了一个非常好的解决方案,它把每个要装饰的功能放在单独的类中,并让这个类包装它要装饰的对象,对此,当需要执行特殊行为时,客户代码就可以在运行时根据需要选择地,按顺序地使用装饰功能包装对象了。

装饰模式的优点就是把类中的装饰功能从类中搬移去除,这样可以简化原有的类。有效地把类的核心职责和装饰功能区分开了,而且可以去除相关类中的重复的装饰逻辑。

github示例:https://github.com/yangpanyao/design-patterns/tree/master/Decorator
参考资料 装饰模式

Last modification:June 19th, 2020 at 09:41 pm