Laravel5.3之Decorator Pattern

2022-01-10 09:35:47 浏览数 (1)

说明:Laravel中Middleware的实现主要利用了Decorator Pattern的设计,本文主要先学习下Decorator Pattern如何实现,为后面学习Middleware的设计做个铺垫。Decorator Pattern和Adapter Pattern会有很多相似之处,但相比较于Adapter Pattern重点突出adapter,Decorator Pattern重点突出的是wrapper,两个不是同一概念。

开发环境:Laravel5.3 PHP7 OS X 10.11

Decorator Pattern

Decorator Pattern作为一种结构型模式,可以给现有对象Component装饰decorate几个feature,而不影响原有的Component对象,这几个feature就是装饰对象Decorator。这种设计很好用,因为可以随时增加或减少想要的feature,并且增加或减少这种操作又很简单,实现了程序松耦合。就像Laravel中每一个middleware就是一个feature,如果想要增加一个不缓存request的feature,可以增加一个middleware假设叫做NoCacheMiddleware,写好后只需要在app/Http/Kernel.php文件中添加下配置就可。看下一个简单的demo实例,看看如何使用Decorator Pattern。先定义一个IMiddleware的接口,保证设计的features都是同一物种,即只有实现了该接口的feature才称为middleware:

代码语言:javascript复制
namespace MyRightCapitalDevelopmentDecoratorPattern;

interface IMiddleware
{
    public function handle();
}

在该接口中定义一个handle()函数,每一个feature必须实现这个handle()来做逻辑。现在需要设计5个features,并且每一个feature都必须是middleware:

代码语言:javascript复制
$features = [
    CheckForMaintenanceMode::class,
    AddQueuedCookiesToResponse::class,
    StartSession::class,
    ShareErrorsFromSession::class,
    VerifyCsrfToken::class,
];

OK,现在实现第一个feature,并改造为middleware:

代码语言:javascript复制
namespace MyRightCapitalDevelopmentDecoratorPattern;

class CheckForMaintenanceMode implements IMiddleware
{
    /**
     * @var MyRightCapitalDevelopmentDecoratorPatternIMiddleware
     */
    private $middleware;

    /**
     * CheckForMaintenanceMode constructor.
     *
     * @param MyRightCapitalDevelopmentDecoratorPatternIMiddleware $middleware
     */
    public function __construct(IMiddleware $middleware)
    {
        $this->middleware = $middleware;
    }
    
    public function handle()
    {
        echo 'Check if the application is in the maintenance status.' . PHP_EOL;
        $this->middleware->handle();
    }
}

第一个middleware是CheckForMaintenanceMode,需要检查程序是否处于维护模式。实现第二个feature,并改造为middleware:

代码语言:javascript复制
namespace MyRightCapitalDevelopmentDecoratorPattern;

class AddQueuedCookiesToResponse implements IMiddleware
{
    /**
     * @var MyRightCapitalDevelopmentDecoratorPatternIMiddleware
     */
    private $middleware;

    /**
     * AddQueuedCookiesToResponse constructor.
     *
     * @param MyRightCapitalDevelopmentDecoratorPatternIMiddleware $middleware
     */
    public function __construct(IMiddleware $middleware)
    {
        $this->middleware = $middleware;
    }
    
    public function handle()
    {
        $this->middleware->handle();
        echo 'Add queued cookies to the response' . PHP_EOL;
    }
}

第二个middleware实现把cookie添加到response。实现第三个feature,并改造为middleware:

代码语言:javascript复制
namespace MyRightCapitalDevelopmentDecoratorPattern;

class StartSession implements IMiddleware
{
    /**
     * @var MyRightCapitalDevelopmentDecoratorPatternIMiddleware
     */
    private $middleware;

    /**
     * StartSession constructor.
     *
     * @param MyRightCapitalDevelopmentDecoratorPatternIMiddleware $middleware
     */
    public function __construct(IMiddleware $middleware)
    {
        $this->middleware = $middleware;
    }
    
    public function handle()
    {
        echo 'Start session of this request.' . PHP_EOL;
        $this->middleware->handle();
        echo 'Close session of this request.' . PHP_EOL;
    }
}

第三个feature主要实现开启和关闭session。实现第四个feature,并改造为middleware:

代码语言:javascript复制
class ShareErrorsFromSession implements IMiddleware
{
    /**
     * @var MyRightCapitalDevelopmentDecoratorPatternIMiddleware
     */
    private $middleware;

    /**
     * ShareErrorsFromSession constructor.
     *
     * @param MyRightCapitalDevelopmentDecoratorPatternIMiddleware $middleware
     */
    public function __construct(IMiddleware $middleware)
    {
        $this->middleware = $middleware;
    }
    
    public function handle()
    {
        $this->middleware->handle();
        echo 'Share the errors variable from request to the views.' . PHP_EOL;
    }
}

第四个feature主要实现共享变量$errors,以便在视图中使用该变量。实现第五个feature,并改造为middleware:

代码语言:javascript复制
namespace MyRightCapitalDevelopmentDecoratorPattern;

class VerifyCsrfToken implements IMiddleware
{
    /**
     * @var MyRightCapitalDevelopmentDecoratorPatternIMiddleware
     */
    private $middleware;

    /**
     * VerifyCsrfToken constructor.
     *
     * @param MyRightCapitalDevelopmentDecoratorPatternIMiddleware $middleware
     */
    public function __construct(IMiddleware $middleware)
    {
        $this->middleware = $middleware;
    }
    
    public function handle()
    {
        echo 'Verify csrf token when post request.' . PHP_EOL;
        $this->middleware->handle();
    }
}

第五个feature主要实现CSRF验证。OK,现在每一个feature都已经实现了,并将作为Decorator来装饰初始的Component。

OK,Decorator Pattern中已经有了五个Decorators,现在需要实现一个Component,然后用这五个Decorators来装饰Component。现在定义一个Component接口,保证Component与Decorator是相似物种,并且Component又有自己的实现接口:

代码语言:javascript复制
namespace MyRightCapitalDevelopmentDecoratorPattern;

interface IComponent extends IMiddleware
{
    public function getRequest();
}

现在构造一个Component:

代码语言:javascript复制
namespace MyRightCapitalDevelopmentDecoratorPattern;

class Request implements IComponent
{
    public function handle()
    {
        echo 'This is a request from the client. And this request will go through the middlewares.' . PHP_EOL;
    }

    public function getRequest()
    {
        return $this;
    }
}

OK,在Decorator Pattern中,目前已经构造好了Component和Decorator。把Component和Decorator拼接在一起的场所是Client,所以需要造一个Client类,在其内部实现对Component的Decorate操作:

代码语言:javascript复制
namespace MyRightCapitalDevelopmentDecoratorPattern;

class Client
{
    /**
     * @var MyRightCapitalDevelopmentDecoratorPatternRequest
     */
    protected $request;

    /**
     * @var MyRightCapitalDevelopmentDecoratorPatternIMiddleware
     */
    protected $response;

    public function __construct()
    {
        // Component
        $this->request  = new Request();
        
        // Decorate the Component
        $this->response = $this->wrapDecorator($this->request);
    }

    /**
     * @param MyRightCapitalDevelopmentDecoratorPatternIMiddleware $decorator
     *
     * @return MyRightCapitalDevelopmentDecoratorPatternIMiddleware
     */
    public function wrapDecorator(IMiddleware $decorator)
    {
        $decorator = new VerifyCsrfToken($decorator);
        $decorator = new ShareErrorsFromSession($decorator);
        $decorator = new StartSession($decorator);
        $decorator = new AddQueuedCookiesToResponse($decorator);
        $response  = new CheckForMaintenanceMode($decorator);

        return $response;
    }

    /**
     * @return MyRightCapitalDevelopmentDecoratorPatternIMiddleware
     */
    public function getResponse()
    {
        return $this->response->handle();
    }
}

Client中wrapDecorator()实现了把原有的Component进过5个Middlewares的装饰后得到的新的Component,新的Component还是IMiddleware的实现,还是原来的物种。整个UML图:

OK,现在执行整个Decorator Pattern,看看是不是这些middlewares已经被装饰进原来的Component,创建一个index.php文件:

代码语言:javascript复制
// 加载composer的autoload.php文件
include __DIR__ . '/../../../vendor/autoload.php';

$client = new MyRightCapitalDevelopmentDecoratorPatternClient();
$client->getResponse();

php index.php文件看看输出什么:

代码语言:javascript复制
Check if the application is in the maintenance status.
Start session of this request.
Verify csrf token when post request.
This is a request from the client. And this request will go through the middlewares.
Share the errors variable from request to the views.
Close session of this request.
Add queued cookies to the response.

的确,五个middlewares已经装饰了原有的component,并检查下装饰次序是否是正确的?实际上,Client中的$this->response等同于:

代码语言:javascript复制
$response = new CheckForMaintenanceMode(
                new AddQueuedCookiesToResponse(
                    new StartSession(
                        new ShareErrorsFromSession(
                            new VerifyCsrfToken(
                                new Request()
                        )
                    )
                )
            )
        );

所以,执行次序是:

代码语言:javascript复制
1. CheckForMaintenanceMode::handle() -> 先执行 echo 'Check if the application is in the maintenance status.', 然后执行 AddQueuedCookiesToResponse::handle()
2. AddQueuedCookiesToResponse::handle() -> 先执行 StartSession::handle(), 然后执行 echo 'Add queued cookies to the response.'
3. StartSession::handle() -> 先执行 echo 'Start session of this request.', 然后执行 ShareErrorsFromSession::handle(), 最后执行 echo 'Close session of this request.'
4. ShareErrorsFromSession::handle() -> 先执行VerifyCsrfToken::handle(), 然后执行 echo 'Share the errors variable from request to the views.'
5. VerifyCsrfToken::handle() -> 先执行 echo 'Verify csrf token when post request.', 然后执行 Request::handle()
6. Request::handle() -> 执行 echo 'This is a request from the client. And this request will go through the middlewares.'

// So,执行顺序等同于:
echo 'Check if the application is in the maintenance status.' -> 
echo 'Start session of this request.' -> 
echo 'Verify csrf token when post request.' -> 
echo 'This is a request from the client. And this request will go through the middlewares.' ->
echo 'Share the errors variable from request to the views.' ->
echo 'Close session of this request.' ->
echo 'Add queued cookies to the response.' ->

在Laravel里每一个Middleware中有前置操作和后置操作。在本demo里echo语句前置于$this->middleware->handle();则为前置操作,后置则为后置操作。 OK,再加一个Kernel类,保证Request经过Middleware的前置操作后进入Kernel,然后从Kernel出来进入Middlewares的后置操作,一步步过滤:

代码语言:javascript复制
namespace MyRightCapitalDevelopmentDecoratorPattern;

interface IKernel extends IMiddleware
{

}

class Kernel implements IKernel
{
    public function handle()
    {
        echo 'Kernel handle the request, and send the response.' . PHP_EOL;
    }
}

// 修改Request
class Request implements IRequest
{
    /**
     * @var MyRightCapitalDevelopmentDecoratorPatternIKernel
     */
    private $kernel;

    public function __construct(IKernel $kernel)
    {
        $this->kernel = $kernel;
    }

    public function handle()
    {
        echo 'This request has been filtering by the before action in the middlewares, and go into the kernel.' . PHP_EOL;
        $this->kernel->handle();
        echo 'The request has been handled by the kernel, and will be send to the after action in the middlewares' . PHP_EOL;
    }

    public function getRequest()
    {
        return $this;
    }
}

// 修改下Client的构造函数
public function __construct()
    {
        // Component
        $this->request = new Request(new Kernel());

        // Decorate the Component
        $this->response = $this->wrapDecorator($this->request);
    }

则再次执行index.php文件,得到:

代码语言:javascript复制
Check if the application is in the maintenance status.
Start session of this request.
Verify csrf token when post request.
This request has been filtering by the before action in the middlewares, and go into the kernel.
Kernel handle the request, and send the response.
The request has been handled by the kernel, and will be send to the after action in the middlewares
Share the errors variable from request to the views.
Close session of this request.
Add queued cookies to the response.

具体流程上文已经讨论,可画一张草图展示处理流程,其中Before表示该Middleware的前置操作,After表示该Middleware的后置操作:

OK,使用Decorator Pattern来层层过滤Request,并实现分层,最后进入Kernel执行得到Response,然后Response经过层层过滤,返回给客户端。非常赞的设计。

总结:本文主要学习Laravel如何使用Decorator Pattern来设计Middleware。下一篇学习下Laravel中Middleware的源码。

0 人点赞