装饰模式 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 网站上。

上一篇 下一篇
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 0
发起讨论 只看当前版本


暂无话题~