文档章节

Laravel5.3之Decorator Pattern

botkenni
 botkenni
发布于 2016/10/27 17:14
字数 1873
阅读 8
收藏 0

说明: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:

namespace MyRightCapital\Development\DecoratorPattern;

interface IMiddleware
{
    public function handle();
}

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

$features = [
    CheckForMaintenanceMode::class,
    AddQueuedCookiesToResponse::class,
    StartSession::class,
    ShareErrorsFromSession::class,
    VerifyCsrfToken::class,
];

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

namespace MyRightCapital\Development\DecoratorPattern;

class CheckForMaintenanceMode implements IMiddleware
{
    /**
     * @var \MyRightCapital\Development\DecoratorPattern\IMiddleware
     */
    private $middleware;

    /**
     * CheckForMaintenanceMode constructor.
     *
     * @param \MyRightCapital\Development\DecoratorPattern\IMiddleware $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:

namespace MyRightCapital\Development\DecoratorPattern;

class AddQueuedCookiesToResponse implements IMiddleware
{
    /**
     * @var \MyRightCapital\Development\DecoratorPattern\IMiddleware
     */
    private $middleware;

    /**
     * AddQueuedCookiesToResponse constructor.
     *
     * @param \MyRightCapital\Development\DecoratorPattern\IMiddleware $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:

namespace MyRightCapital\Development\DecoratorPattern;

class StartSession implements IMiddleware
{
    /**
     * @var \MyRightCapital\Development\DecoratorPattern\IMiddleware
     */
    private $middleware;

    /**
     * StartSession constructor.
     *
     * @param \MyRightCapital\Development\DecoratorPattern\IMiddleware $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:

class ShareErrorsFromSession implements IMiddleware
{
    /**
     * @var \MyRightCapital\Development\DecoratorPattern\IMiddleware
     */
    private $middleware;

    /**
     * ShareErrorsFromSession constructor.
     *
     * @param \MyRightCapital\Development\DecoratorPattern\IMiddleware $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:

namespace MyRightCapital\Development\DecoratorPattern;

class VerifyCsrfToken implements IMiddleware
{
    /**
     * @var \MyRightCapital\Development\DecoratorPattern\IMiddleware
     */
    private $middleware;

    /**
     * VerifyCsrfToken constructor.
     *
     * @param \MyRightCapital\Development\DecoratorPattern\IMiddleware $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又有自己的实现接口:

namespace MyRightCapital\Development\DecoratorPattern;

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

现在构造一个Component:

namespace MyRightCapital\Development\DecoratorPattern;

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操作:

namespace MyRightCapital\Development\DecoratorPattern;

class Client
{
    /**
     * @var \MyRightCapital\Development\DecoratorPattern\Request
     */
    protected $request;

    /**
     * @var \MyRightCapital\Development\DecoratorPattern\IMiddleware
     */
    protected $response;

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

    /**
     * @param \MyRightCapital\Development\DecoratorPattern\IMiddleware $decorator
     *
     * @return \MyRightCapital\Development\DecoratorPattern\IMiddleware
     */
    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 \MyRightCapital\Development\DecoratorPattern\IMiddleware
     */
    public function getResponse()
    {
        return $this->response->handle();
    }
}

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

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

// 加载composer的autoload.php文件
include __DIR__ . '/../../../vendor/autoload.php';

$client = new \MyRightCapital\Development\DecoratorPattern\Client();
$client->getResponse();

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

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等同于:

$response = new CheckForMaintenanceMode(
                new AddQueuedCookiesToResponse(
                    new StartSession(
                        new ShareErrorsFromSession(
                            new VerifyCsrfToken(
                                new Request()
                        )
                    )
                )
            )
        );

所以,执行次序是:

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的后置操作,一步步过滤:

namespace MyRightCapital\Development\DecoratorPattern;

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 \MyRightCapital\Development\DecoratorPattern\IKernel
     */
    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文件,得到:

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的源码。

© 著作权归作者所有

共有 人打赏支持
botkenni
粉丝 20
博文 409
码字总数 434882
作品 0
西城
程序员
私信 提问
Laravel5.3之Middleware源码解析

说明:本文主要学习Laravel的Middleware的源码设计思想,并将学习心得分享出来,希望对别人有所帮助。Laravel5.3之Decorator Pattern已经聊过Laravel使用了Decorator Pattern来设计Middlewar...

botkenni
2016/10/27
37
0
sitemesh简单使用

一、简介 SiteMesh 是一个网页布局和修饰的框架,利用它可以将网页的内容和页面结构分离,以达到页面结构共享的目的。 官网:http://wiki.sitemesh.org/wiki/display/sitemesh/Home 二、配置...

火龙战士
2016/03/11
49
0
sitemesh只渲染一个路径

我的decorators.xml如下 /static/* /admin/index /admin/* /admin/**/* /blog/* /index /index/* / 启动项目后sitemesh始终只渲染edudefault,admindefault的不渲染,admindefault的效果还是......

淡看江湖
2016/05/30
158
0
收集的关于设计模式的java例子集合

终于有功夫坐下来把所有Design Pattern好好整理一通了,这可是个盘算了快十年的想法了。 ==============================================================================================...

晨曦之光
2012/05/21
832
0
使用SiteMesh简化网页布局

在公司项目使用了 Appfuse ,其带有 SiteMesh 对于网页布局简化让我感觉很好用,本文旨在对对 Sitemesh 的基本原理和在项目中使用 Sitemesh 的实现流程、使用技巧的介绍。 1. 基本原理 Site...

红薯
2008/12/01
3.9K
2

没有更多内容

加载失败,请刷新页面

加载更多

如何开发一款以太坊(安卓)钱包系列2 - 导入账号及账号管理

这是如何开发一款以太坊(安卓)钱包系列第2篇,如何导入账号。有时用户可能已经有一个账号,这篇文章接来介绍下,如何实现导入用户已经存在的账号。 导入账号预备知识 从用户需求上来讲,导...

Tiny熊
今天
2
0
intellJ IDEA搭建java+selenium自动化环境(maven,selenium,testng)

1.安装jdk1.8; 2.安装intellJ; 3.安装maven; 3.1 如果是单前用户,配置用户环境变量即可,如果是多用户,则需配置系统环境变量,变量名为MAVEN_HOME,赋值D:\Application\maven,往path中...

不最醉不龟归
今天
4
0
聊聊ShenandoahGC的Brooks Pointers

序 本文主要研究一下ShenandoahGC的Brooks Pointers Shenandoah Shenandoah面向low-pause-time的垃圾收集器,它的GC cycle主要有 Snapshot-at-the-beginning concurrent mark包括Init Mark(P......

go4it
昨天
4
0
Makefile通用编写规则

#简单实用的Makefile模板: objs := a.o b.o test:$(objs) gcc -o test $^ # .a.o.d .b.o.d dep_files := $(foreach f,$(objs),.$(f).d) dep_files := $(wildcard $(dep_files)) ifneq ($(d......

shzwork
昨天
3
0
《万历十五年》的读后感作文4000字

《万历十五年》的读后感作文4000字: 万历十五年,即1587年,距今已过去432年。在明朝276的历史中,这一年很平淡,并没有什么特别之处。黄仁宇的《万历十五年》一书,有别于其他的历史叙述方...

原创小博客
昨天
2
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部