责任链模式

202 阅读2分钟

责任链模式

属于行为型设计模式之一,通俗解释:它有助于构建一系列对象。请求从一端进入并持续从一个对象到另一个对象,直到找到合适的处理程序,逻辑上有点像单链表。

PHP中使用

在 PHP 世界中, 责任链 (Co­R) 模式在 HTTP 请求中间件中的使用是最广为人知的。 绝大多数流行的 PHP 框架都会使用该模式来实现 HTTP 请求中间件, 它甚至被标准化而成为了 PSR-15 的一个组成部分。

它的运作方式是这样的: HTTP 请求必须通过一个中间件对象堆栈才能被程序处理。 每个中间件可以拒绝进一步处理该请求, 也可以将其传递给下一个中间件。 当请求成功通过所有中间件后, 程序的主处理者才能最终对其进行处理。

你可能已经注意到这种方法在某种程度上来说颠倒了该模式的原始意图。 的确, 在通常的实现中, 只有在当前处理者无法对请求进行处理时, 请求才能沿着链进行传递; 而中间件认为程序可以处理该请求时, 才会沿着链将其传递下去。 尽管如此, 由于中间件是相互连接的, 所以整个概念仍被认为是责任链模式的示例。

案例

例如,你的帐户设置有三种付款方式(ABC); 每个都有不同的额度。A有 100 美元,B具有 300 美元和C具有 1000 美元,以及支付偏好被选择作为先AB然后C。你试着购买价值 210 美元的东西。使用责任链,首先A会检查帐户是否可以进行购买,如果是,则进行购买并且责任链破裂。如果不能购买,请求将转发到帐户B来检查金额,如果能购买,责任链破裂,否则请求将继续转发,直到找到合适的处理程序。在这里ABC 是链条的链接,整个现象是责任链。

类图

image.png

程序实例

翻译上面的帐户示例。首先,我们有一个包含将帐户链接在一起的逻辑的基本帐户和一些帐户

abstract class Account
{
    protected $successor;
    protected $balance;

    public function setNext(Account $account)
    {
        $this->successor = $account;
    }

    public function pay(float $amountToPay)
    {
        if ($this->canPay($amountToPay)) {
            echo sprintf('Paid %s using %s' . PHP_EOL, $amountToPay, get_called_class());
        } elseif ($this->successor) {
            echo sprintf('Cannot pay using %s. Proceeding ..' . PHP_EOL, get_called_class());
            $this->successor->pay($amountToPay);
        } else {
            throw new Exception('None of the accounts have enough balance');
        }
    }

    public function canPay($amount): bool
    {
        return $this->balance >= $amount;
    }
}

class Bank extends Account
{
    protected $balance;

    public function __construct(float $balance)
    {
        $this->balance = $balance;
    }
}

class Paypal extends Account
{
    protected $balance;

    public function __construct(float $balance)
    {
        $this->balance = $balance;
    }
}

class Bitcoin extends Account
{
    protected $balance;

    public function __construct(float $balance)
    {
        $this->balance = $balance;
    }
}

现在让我们使用上面定义的链接准备责任链(即 Bank,Paypal,Bitcoin)

// Let's prepare a chain like below
//      $bank->$paypal->$bitcoin
//
// First priority bank
//      If bank can't pay then paypal
//      If paypal can't pay then bit coin

$bank = new Bank(100);          // Bank with balance 100
$paypal = new Paypal(200);      // Paypal with balance 200
$bitcoin = new Bitcoin(300);    // Bitcoin with balance 300

$bank->setNext($paypal);
$paypal->setNext($bitcoin);

// Let's try to pay using the first priority i.e. bank
$bank->pay(259);

// Output will be
// ==============
// Cannot pay using bank. Proceeding ..
// Cannot pay using paypal. Proceeding ..:
// Paid 259 using Bitcoin!