【PHPデザインパターン】10_Chain of Responsibility~処理のたらい回し


引用記事

この記事を書くきっかけになったブログです。

記事内の解説やソースコードは、こちらのブログと著者の公開リポジトリを参考にしています。

Do You PHP はてな〜[doyouphp][phpdp]PHPによるデザインパターン入門 - Chain of Responsibility~処理のたらい回し

概要

  • 「Chain of Responsibility」は直訳すると「責任の鎖」。
  • 自分の責任で対処する必要かどうかを判断し、自分で対処できない場合は他に任せる。
  • 「処理を依頼する側」と「実際に処理をおこなう側」を分離する。
  • 実際に処理を行う複数のオブジェクトをチェーン状につなぎ、あるオブジェクトがその要求を処理するまで、そのチェーンに沿って要求を渡していく。

構成要素

Handlerクラス

  • 処理オブジェクトの親クラスに相当し、要求を処理するためのAPIを定義する。
  • 内部にHandler型のオブジェクトを保持し、自分が処理できなかった場合はこのオブジェクトが処理を行うことになる。

ConcreteHandlerクラス

  • Handlerクラスのサブクラス。
  • 自分が担当する処理だけを実装する。

Clientクラス

  • チェーンを構成しているConcreteHandlerクラスに処理を送信するクラス。

実演

処理の流れ

  • 渡されたデータが存在するか、半角英字であるか、半角数字であるか検証する。
  • 各バリデーションを通過しなかった場合は、そこで処理を終了しメッセージを出力する。

ファイル構造

 MyChainOfResponsibility
   ├── AlphabetValidationHandler.php
   ├── NotNullValidationHandler.php
   ├── NumberValidationHandler.php
   ├── ValidationHandler.php
   └── my_client.php

ソースコード

Handlerクラス

ValidationHandler.php

ValidationHandler.php
<?php
namespace DoYouPhp\PhpDesignPattern\ChainOfResponsibility\MyChainOfResponsibility;

// Handlerクラスに相当する
// 抽象メソッドの実装は、各バリデーションクラスで行う
abstract class ValidationHandler
{
    // 次回以降のバリデーションクラスのオブジェクトを保有する
    private $next_handlers;

    public function __construct()
    {
        $this->next_handlers = array();
    }

    // 次回以降のバリデーションクラスのオブジェクトを取得し、プロパティにセットする
    public function setHandler(ValidationHandler $handler)
    {
        // 既存の配列を取得する
        $handler_array = $this->next_handlers;
        // 配列の要素を追加する
        $handler_array[] = $handler;

        // 再度代入する
        $this->next_handlers = $handler_array;
    }

    // チェーンを実行する
    // 次回以降のバリデーションはforeach文で実行する
    public function validate($input)
    {
        // 初回のみ
        $result = $this->execValidation($input);
        if (!$result) {
            return $this->getErrorMessage();
        }

        // 次回以降
        foreach ($this->next_handlers as $handler) {
            $result = $handler->execValidation($input);
            if (!$result) {
                return $handler->getErrorMessage();
            }
        }

        return true;
    }

    // 自クラスが担当する処理を実行する
    abstract protected function execValidation($input);

    // 処理失敗時のメッセージを取得する
    abstract protected function getErrorMessage();
}

ConcreteHandlerクラス

NotNullValidationHandler.php

NotNullValidationHandler.php
<?php
namespace DoYouPhp\PhpDesignPattern\ChainOfResponsibility\MyChainOfResponsibility;

use DoYouPhp\PhpDesignPattern\ChainOfResponsibility\MyChainOfResponsibility\ValidationHandler;

// ConcreteHandlerクラスに相当する
// データが存在するか検証する
class NotNullValidationHandler extends ValidationHandler
{
    // 自クラスが担当する処理を実行する
    protected function execValidation($input)
    {
        return (is_string($input) && $input !== '');
    }

    // 処理失敗時のメッセージを取得する
    protected function getErrorMessage()
    {
        return '入力されていません';
    }
}

AlphabetValidationHandler.php

AlphabetValidationHandler.php
<?php
namespace DoYouPhp\PhpDesignPattern\ChainOfResponsibility\MyChainOfResponsibility;

use DoYouPhp\PhpDesignPattern\ChainOfResponsibility\MyChainOfResponsibility\ValidationHandler;

// ConcreteHandlerクラスに相当する
// データが半角英字であるか検証する
class AlphabetValidationHandler extends ValidationHandler
{
    // 自クラスが担当する処理を実行する
    protected function execValidation($input)
    {
        // マッチする場合は1を返す
        // パターン修飾子にiが使われているため、大文字小文字の両方にマッチする
        return preg_match('/^[a-z]*$/i', $input);
    }

    // 処理失敗時のメッセージを取得する
    protected function getErrorMessage()
    {
        return '半角英字で入力してください';
    }
}

NumberValidationHandler.php

NumberValidationHandler.php
<?php
namespace DoYouPhp\PhpDesignPattern\ChainOfResponsibility\MyChainOfResponsibility;

use DoYouPhp\PhpDesignPattern\ChainOfResponsibility\MyChainOfResponsibility\ValidationHandler;

// ConcreteHandlerクラスに相当する
// データが半角数字であるか検証する
class NumberValidationHandler extends ValidationHandler
{
    // 自クラスが担当する処理を実行する
    protected function execValidation($input)
    {
        // マッチする場合は1を返す
        return preg_match('/^[0-9]*$/', $input);
    }

    // 処理失敗時のメッセージを取得する
    protected function getErrorMessage()
    {
        return '半角数字で入力してください';
    }
}

Clientクラス

my_client.php

my_client.php
<?php
namespace DoYouPhp\PhpDesignPattern\ChainOfResponsibility\MyChainOfResponsibility;

require dirname(dirname(__DIR__)).'/vendor/autoload.php';

use DoYouPhp\PhpDesignPattern\ChainOfResponsibility\MyChainOfResponsibility\NotNullValidationHandler;
use DoYouPhp\PhpDesignPattern\ChainOfResponsibility\MyChainOfResponsibility\AlphabetValidationHandler;
use DoYouPhp\PhpDesignPattern\ChainOfResponsibility\MyChainOfResponsibility\NumberValidationHandler;

function validate($input)
{
    // チェーンを作成する
    // 継承元は全てValidationHandlerクラス
    // setHandlerメソッドを利用してバリデーションを追加する
    // チェーンを動的に切り替えたい場合は、引数を追加してswitch文を利用するなどの方法がある
    $handler = new NotNullValidationHandler();

    $handler->setHandler(new AlphabetValidationHandler());
    $handler->setHandler(new NumberValidationHandler());

    return $handler->validate($input);
}

// バリデーションに応じたメッセージを表示する
function showMessage($data)
{
    // ファイル内のvalidateメソッドを呼び出す
    $result = validate($data);

    if ($result === false) {
        echo  'データの検証ができませんでした!';
    } elseif (is_string($result) && $result !== '') {
        echo $result;
    } else {
        echo 'データの検証が完了しました!';
    }

    echo '<br>'."\n";
}

showMessage('');
showMessage('Test');
showMessage('0123456789');