装饰模式 Decorator Pattern

未匹配的标注

定义

动态地给一个对象增加一些额外的职责,就增加对象功能来说,装饰模式比生成子类实现更为灵活。这样可以给某个对象而不是整个类添加一些功能。

设计的原则和思想

  1. 解耦的是装饰者和被装饰者。
  2. 不变部分是被装饰者,变化部分是装饰者。
  3. 核心思想是对对象自身功能进行增强。

一句话概括设计模式

通过组合的方式,增强对象自身的功能,并且支持多个装饰器嵌套使用。

经典装饰者模式

结构中包含的角色

  1. Component(抽象构件)
  2. ConcreteComponent(具体构件)
  3. Decorator(抽象装饰类)
  4. ConcreteDecorator(具体装饰类)

最小可表达代码

// 抽象构件类
abstract class Component
{
    public abstract function display();
}

// 具体构件类
class ConcreteComponent extends Component
{
    public function display()
    {
        echo ' 具体构件 ';
    }
}

// 抽象装饰类
abstract class ComponentDecorator extends Component
{
    protected $component;

    public function __construct(Component $component)
    {  
        $this->component = $component;
    }
}

// 具体装饰类
class ConcreteDecorator extends  ComponentDecorator
{
    public function display()
    {
        echo ' 装饰前的行为 ';
        $this->component->display();
        echo ' 装饰后的行为 ';
    }
}

$decorator = new ConcreteDecorator(new ConcreteComponent);
$decorator->display();

Laravel管道闭包方式的装饰者模式

结构中包含的角色

  1. MiddlewareManager (中间件管理类/修饰者管理类)
  2. Middleware (抽象中间件/抽象装饰者)
  3. ConcreteMiddleWare (具体中间件/具体装饰者)
  4. Controller (控制器/被装饰者)
  5. Request (请求类)

最小可表达代码

class MiddlewareManager
{
    // 请求
    protected $request;

    // 中间件 middlewares
    protected $middlewares = [];

    // 设置请求
    public function send($request)
    {
        $this->request = $request;

        return $this;
    }

    // 设置中间件
    public function through($middlewares)
    {
        $this->middlewares = is_array($middlewares) ? $middlewares : func_get_args();

        return $this;
    }

    // 输入需要执行的控制器,并且开始装饰控制器
    public function then(Closure $controllerClosure)
    {
        $middlewareClosure = array_reduce(
            array_reverse($this->middlewares), $this->carry(), $this->prepareDestination($controllerClosure)
        );

        // 很多人不太熟悉 array_reduce 的用法,这里附上 foreach 的写法,方便理解。
        // $middlewareClosure = $this->prepareDestination($controllerClosure);
        // foreach (array_reverse($this->middlewares) as $middleware) {
        //     $middlewareClosure = function ($request) use ($middlewareClosure, $middleware) {
        //         return $middleware->handle($request, $middlewareClosure);
        //     };
        // }

        return $middlewareClosure($this->request);
    }

    // 设置请求作为参数传给控制器
    protected function prepareDestination(Closure $controllerClosure)
    {
        return function ($request) use ($controllerClosure) {
            return $controllerClosure($request);
        };
    }

    // 执行多个中间件的方法
    protected function carry()
    {
        return function ($controllerClosure, $middleware) {
            return function ($request) use ($controllerClosure, $middleware) {
                return $middleware->handle($request, $controllerClosure);
            };
        };
    }
}

class Request{}

interface Middleware
{
    public function handle(Request $request, Closure $controllerClosure);
}

class ConcreteMiddleWare implements Middleware
{
    public function handle(Request $request, Closure $controllerClosure)
    {
        var_dump("前");

        $response = $controllerClosure($request);

        var_dump("后");

        return $response;
    }
}

class Controller
{
    public function test(Request $request)
    {
        var_dump("执行控制器的方法");

        return ['status' => 200];
    }
}

$response = (new MiddlewareManager)
    ->send(new Request)
    ->through([new ConcreteMiddleWare])
    ->then(function ($request) {
        return (new Controller)->test($request);
    });

var_dump($response);

优点

  1. 你可以在运行时添加或删除对象的功能。
  2. 你可以用多个装饰封装对象来组合几种行为。
  3. 装饰类和被装饰类可以独立发展,不会相互耦合。
  4. 通过组合来替代继承,使用对象之间的关联关系取代类之间的继承关系。

缺点

  1. 在封装器栈中删除指定装饰器比较困难。
  2. 实现行为不受装饰栈顺序影响的装饰比较困难。
  3. 比继承更容易出错,排错也比较困难,代码较为繁琐。
  4. 多层装饰比较复杂。

何时使用

  1. 扩展一个类的功能。
  2. 动态增加功能。
  3. 当不能采用继承的方式(final)对系统进行扩展或者采用继承不利于系统扩展和维护(大量独立扩展)

实际应用场景

  1. Laravel的中间件。
  2. 汉堡包,加番茄酱,加牛肉,加鸡蛋。。。
  3. Java的IO流。

本文章首发在 LearnKu.com 网站上。

上一篇 下一篇
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 0
发起讨论 查看所有版本


暂无话题~