职责链模式也叫责任链模式。职责连模式的英文翻译是 Chain Of Responsibility Design Pattern。在 GoF 的《设计模式》中,它是这么定义的:

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

翻译成中文就是:将请求的发送和接收解耦,让多个接收对象都有机会处理这个请求。将这些接收对象串成一条链,并沿着这条链传递这个请求,直到链上的某个接收对象能够处理它为止。
这么说比较抽象,我用更加容易理解的话来进一步解读一下。
在职责链模式中,多个处理器(也就是刚刚定义中说的“接收对象”)依次处理同一个请求。一个请求先经过 A 处理器处理,然后再把请求传递给 B 处理器,B 处理器处理完后再传递给 C 处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作职责链模式。

职责链模式的结构与实现

结构

  • 抽象处理者角色(Handler):它定义了一个处理请求的接口。当然对于链子的不同实现,也可以在这个角色中实现后继链。
  • 具体处理者角色(Concrete Handler):实现抽象角色中定义的接口,并处理它所负责的请求。如果不能处理则访问它的后继者。

代码实现

Handler.php

<?php
namespace DesignPatterns\ChainOfResponsibility\example1;
/**
 * Handler类 抽象处理者角色
 * 定义了一个处理请求的接口。当然对于链子的不同实现,也可以在这个角色中实现后继链。
 * Class Handler
 * @package DesignPatterns\ChainOfResponsibility\example1
 */
abstract class Handler
{
    protected $successor;

    /**
     * @param mixed $successor
     */
    public function setSuccessor(Handler $successor)
    {
        $this->successor = $successor;
    }
    //处理请求的方法
    abstract public function HandleRequest($request);

}

ConcreteHandlerA.php

<?php
namespace DesignPatterns\ChainOfResponsibility\example1;
/**
 * ConcreteHandler类,具体处理者类,处理它所负责的请求,可访问它的后继者,
 * 如果可处理该请求,就处理之,否则就将该请求转发给它的后继者。
 * Class ConcreteHandlerA
 * @package DesignPatterns\ChainOfResponsibility\example1
 */
class ConcreteHandlerA extends Handler
{
    public function HandleRequest($request)
    {
        if ($request >=0 && $request < 10)
        {
            echo "ConcreteHandlerA处理的是10到20的参数,该参数是".$request."<br>";
        } else if ($this->successor != null) {
            // 转移
            $this->successor->HandleRequest($request);
        }
    }
}

ConcreteHandlerB.php

<?php
namespace DesignPatterns\ChainOfResponsibility\example1;
/**
 * Class ConcreteHandlerB
 * @package DesignPatterns\ChainOfResponsibility\example1
 */
class ConcreteHandlerB extends Handler
{

    public function HandleRequest($request)
    {
        if ($request >=10 && $request < 20)
        {
            echo "ConcreteHandlerB处理的是10到20的参数,该参数是".$request."<br>";
        } else if ($this->successor != null) {
            // 转移
            $this->successor->HandleRequest($request);
        }
    }
}
}

ConcreteHandlerC.php

<?php
namespace DesignPatterns\ChainOfResponsibility\example1;
/**
 * Class ConcreteHandlerC
 * @package DesignPatterns\ChainOfResponsibility\example1
 */
class ConcreteHandlerC extends Handler
{
    public function HandleRequest($request)
    {
        if ($request >=20 && $request < 30)
        {
            echo "ConcreteHandlerC处理的是20到30的参数,该参数是".$request."<br>";
        } else if ($this->successor != null) {
            // 转移
            $this->successor->HandleRequest($request);
        }
    }
}

运行 Client.php

<?php
namespace DesignPatterns\ChainOfResponsibility\example1;
require dirname(__DIR__).'/../vendor/autoload.php';
class Client
{
    public function run()
    {
        $h1 = new ConcreteHandlerA();
        $h2 = new ConcreteHandlerB();
        $h3 = new ConcreteHandlerC();
        //设置职责链的上家下家
        $h1->setSuccessor($h2);
        $h2->setSuccessor($h3);
        $requests = [2,5,14,22,18,3,27,20];
        //循环给最小处理者提交请求,不同的数额,由不同权限处理者处理
        foreach ($requests as $value) {
            $h1->HandleRequest($value);
        }
    }
}
$worker = new Client();
$worker->run();

运行结果

如上例Handler 是所有处理器类的抽象父类,HandleRequest() 是抽象方法。每个具体的处理器类(ConcreteHandlerA、ConcreteHandlerB)的 HandleRequest() 函数的代码结构类似,如果它能处理该请求,就不继续往下传递;如果不能处理,则交由后面的处理器来处理(也就是调用 $this->successor->HandleRequest($request))。客户端需要依次实例化三个责任链实例,并指定链条成员。创建请求参数,之后通过责任链来进行结果判断。

示例

在 PHP 世界中, 责任链 (CoR) 模式在 HTTP 请求中间件中的使用是最广为人知的。 绝大多数流行的 PHP 框架都会使用该模式来实现 HTTP 请求中间件, 它甚至被标准化而成为了 PSR-15 的一个组成部分。
它的运作方式是这样的: HTTP 请求必须通过一个中间件对象堆栈才能被程序处理。 每个中间件可以拒绝进一步处理该请求, 也可以将其传递给下一个中间件。 当请求成功通过所有中间件后, 程序的主处理者才能最终对其进行处理。
你可能已经注意到这种方法在某种程度上来说颠倒了该模式的原始意图。 的确, 在通常的实现中, 只有在当前处理者无法对请求进行处理时, 请求才能沿着链进行传递; 而中间件认为程序可以处理该请求时, 才会沿着链将其传递下去。 尽管如此, 由于中间件是相互连接的, 所以整个概念仍被认为是责任链模式的示例。
下面是一个使用中间件模拟验证用户登陆的一个简单示例:
Middleware.php

<?php
namespace DesignPatterns\ChainOfResponsibility\example2;
/**
 * 职责链模式为组成链的对象声明了一个角色,即处理程序。
 * 在我们的示例中,让我们区分中间件和最终应用程序的处理程序,后者在请求通过所有中间件时执行对象。
 * 对象基本中间件类声明一个接口,用于将中间件对象链接到链中。
 * Class Middleware
 * @package DesignPatterns\ChainOfResponsibility\example2
 */
abstract class Middleware
{
    /**
     * @var Middleware
     */
    private $next;

    /**
     * 构建中间件对象链
     * @param Middleware $next
     * @return Middleware
     */
    public function linkWith(Middleware $next): Middleware
    {
        $this->next = $next;

        return $next;
    }

    /**
     * 子类必须重写此方法以提供自己的检查。如果子类不能处理一个请求,它可以回退到父实现。
     * @param string $email
     * @param string $password
     * @return bool
     */
    public function check(string $email, string $password): bool
    {
        if (!$this->next) {
            return true;
        }

        return $this->next->check($email, $password);
    }
}

UserExistsMiddleware.php

<?php
namespace DesignPatterns\ChainOfResponsibility\example2;
/**
 * 这个具体的中间件检查具有给定凭据的用户是否存在。
 * Class UserExistsMiddleware
 * @package DesignPatterns\ChainOfResponsibility\example2
 */
class UserExistsMiddleware extends Middleware
{
    private $server;

    public function __construct(Server $server)
    {
        $this->server = $server;
    }

    public function check(string $email, string $password): bool
    {
        if (!$this->server->hasEmail($email)) {
            echo "UserExistsMiddleware: 此邮件未注册!\n";

            return false;
        }

        if (!$this->server->isValidPassword($email, $password)) {
            echo "UserExistsMiddleware: 密码错误\n";

            return false;
        }

        return parent::check($email, $password);
    }
}

RoleCheckMiddleware.php

<?php
namespace DesignPatterns\ChainOfResponsibility\example2;
/**
 * 这个具体的中间件检查与请求相关联的用户是否具有足够的权限。
 * Class RoleCheckMiddleware
 * @package DesignPatterns\ChainOfResponsibility\example2
 */
class RoleCheckMiddleware extends Middleware
{
    public function check(string $email, string $password): bool
    {
        if ($email === "admin@example.com") {
            echo "RoleCheckMiddleware: Hello, admin!\n";

            return true;
        }
        echo "RoleCheckMiddleware: Hello, user!\n";

        return parent::check($email, $password);
    }
}

ThrottlingMiddleware.php

<?php
namespace DesignPatterns\ChainOfResponsibility\example2;
/**
 * 这个具体的中间件检查是否有太多失败的登录请求。
 * Class ThrottlingMiddleware
 * @package DesignPatterns\ChainOfResponsibility\example2
 */
class ThrottlingMiddleware extends Middleware
{
    private $requestPerMinute;//设置可登录失败的请求次数

    private $request;

    private $currentTime;

    public function __construct(int $requestPerMinute)
    {
        $this->requestPerMinute = $requestPerMinute;
        $this->currentTime = time();
    }

    /**
     * @param string $email
     * @param string $password
     * @return bool
     */
    public function check(string $email, string $password): bool
    {
        if (time() > $this->currentTime + 60) {
            $this->request = 0;
            $this->currentTime = time();
        }

        $this->request++;

        if ($this->request > $this->requestPerMinute) {
            echo "ThrottlingMiddleware: 超过请求限制!\n";
            die();
        }

        return parent::check($email, $password);
    }
}

Server.php

<?php
namespace DesignPatterns\ChainOfResponsibility\example2;
/**
 * 这是一个作为实际处理程序的应用程序类。
 * 在启动与请求相关联的某些业务逻辑之前,Server类使用职责链模式执行一组不同的身份验证中间件。
 * Class Server
 * @package DesignPatterns\ChainOfResponsibility\example2
 */
class Server
{
    private $users = [];

    /**
     * @var Middleware
     */
    private $middleware;

    /**
     * 配置中间件对象
     * @param Middleware $middleware
     */
    public function setMiddleware(Middleware $middleware): void
    {
        $this->middleware = $middleware;
    }

    /**
     * 服务器从客户端获取电子邮件和密码并发送对中间件的授权请求。
     * @param string $email
     * @param string $password
     * @return bool
     */
    public function logIn(string $email, string $password): bool
    {
        if ($this->middleware->check($email, $password)) {
            echo "Server: 授权成功!\n";

            // 省略一些后续处理代码

            return true;
        }

        return false;
    }
    //注册用户
    public function register(string $email, string $password): void
    {
        $this->users[$email] = $password;
    }
    //判断邮箱是否存在
    public function hasEmail(string $email): bool
    {
        return isset($this->users[$email]);
    }
    //验证用户密码
    public function isValidPassword(string $email, string $password): bool
    {
        return $this->users[$email] === $password;
    }
}

Client.php

<?php
namespace DesignPatterns\ChainOfResponsibility\example2;
require dirname(__DIR__).'/../vendor/autoload.php';
/**
 * Class Client
 * @package DesignPatterns\ChainOfResponsibility\example2
 */
class Client
{
    public function run()
    {
        $server = new Server();
        $server->register("admin@example.com", "admin_pass");
        $server->register("user@example.com", "user_pass");
        //所有中间件都是链接的。客户端可以根据需要构建不同的链配置。
        $middleware = new ThrottlingMiddleware(2);
        $middleware->linkWith(new UserExistsMiddleware($server))
            ->linkWith(new RoleCheckMiddleware);
        $server->setMiddleware($middleware);

        do {
            echo "\nEnter your email:\n";
            $email = readline();
            echo "Enter your password:\n";
            $password = readline();
            $success = $server->logIn($email, $password);
        } while (!$success);
    }
}
$worker = new Client();
$worker->run();

运行Client.php

注意 parent::check()调用既可以插入此方法的开头,也可以插入结束。这个在所有中间件对象上提供比简单循环更大的灵活性。例如,中间件可以通过在所有其他检查之后运行其检查来更改检查顺序。也就是我们常说的前置中间件,与后置中间件。

总结

在职责链模式中,多个处理器依次处理同一个请求。一个请求先经过 A 处理器处理,然后再把请求传递给 B 处理器,B 处理器处理完后再传递给 C 处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作职责链模式。
职责链模式, 使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对像处理它为止。
当用户提交一个请求时,请求是沿着链传递直至有一个对象负责处理它。

github示例:https://github.com/yangpanyao/design-patterns/tree/master/ChainOfResponsibility
参考资料 《大话设计模式》,《深入设计模式-责任链模式》

Last modification:July 7th, 2020 at 10:52 pm