RitoLabo

ChainOfResponsibilityパターン - PHP デザインパターン

  • 公開:
  • カテゴリ: PHP DesignPatterns
  • タグ: PHP,Behavior,DesignPatterns,ChainOfResponsibility

Chain of Responsibilityパターン(チェイン・オブ・レスポンシビリティパターン)は、振る舞いに関するデザインパターン手法で、同一レベルの処理クラスをチェインする(鎖状につなげる)事で、一定の規則に対応した連鎖的な処理を実現できる処理モデルです。

Chain of Responsibilityパターンの基本的なクラス図は以下になります。

ChainOfResponsibilityパターンクラス図

実装例

今回は、アーケード格闘ゲーム(1P)を例にChain of Responsibilityパターンを実装していきます。

ゲームセンターで一人で格闘ゲームをします。ネット対戦ではなく、相手はコンピュータのみのストーリーモードで、登場する相手に全て勝利出来ればゲームリアです。(きりがないので3人でクリアにします)

ここで、このストーリーモードを全クリする為には、やはりプレイヤーのスキル、いわゆるレベルがある程度高くなければいけません。つまりは、あなたのプレイヤーレベルが一定以上であればこのストーリーモードを全クリ出来るし、そうでなければ、どこかのステージで負けてしまう。という事になります。

つまり、これらを実装する場合には、「誰かに勝てたらまた次、そこで勝てたらまた次、、」といった具合で、全クリの条件が満たされるか負けるまでノンストップで戦わせていく。というロジックになります。

まずはこのバトルの基底クラスを作成します。

App/Handlers/Super/BattleHandler.php
<?php

namespace App\Handlers\Super;

abstract class BattleHandler
{
private $next_handler = null;

public function setHandler(BattleHandler $handler)
{
$this->next_handler = $handler;
return $this;
}

public function getNextHandler()
{
return $this->next_handler;
}

public function battle($level)
{
if (!$this->battleResult($level)) {
return $this->getMessage();
} elseif (!is_null($this->getNextHandler())) {
return $this->getNextHandler()->battle($level);
} else {
return 'You all win! story clear!';
}
}

protected abstract function battleResult($level);

protected abstract function getMessage();
}
  • メンバ変数 $next_handler で受け渡し先のオブジェクトを保持します。
  • setHandler() メソッドで受け渡し先のオブジェクトをセットします。
  • getNextHandler() メソッドで、次のオブジェクトを返却します。
  • battle() メソッドで、バトルの判定を行っています。ここがChain of Responsibilityパターンのポイント部分で、条件によって次へ受け渡したり、処理を停止したりのハンドリングを行っています。
  • battleResult()getMessage() は抽象メソッドとし、それぞれの具象クラスで実装します。

次に具象クラスです。倒すべき敵プレイヤーを3人登場させるので、それらを定義していきます。

App/Handlers/RyuBattleHandler.php
<?php

namespace App\Handlers;

use App\Handlers\Super\BattleHandler;

class RyuBattleHandler extends BattleHandler
{
private $level = 10;

public function battleResult($level)
{
return ($level > $this->level);
}

public function getMessage()
{
return 'Ryu win! You lose.';
}
}
App/Handlers/KenBattleHandler.php
<?php

namespace App\Handlers;

use App\Handlers\Super\BattleHandler;

class KenBattleHandler extends BattleHandler
{
private $level = 15;

public function battleResult($level)
{
return ($level > $this->level);
}

public function getMessage()
{
return 'Ken win! You lose.';
}
}
App/Handlers/HondaBattleHandler.php
<?php

namespace App\Handlers;

use App\Handlers\Super\BattleHandler;

class HondaBattleHandler extends BattleHandler
{
private $level = 20;

public function battleResult($level)
{
return ($level > $this->level);
}

public function getMessage()
{
return 'Honda win! You lose.';
}
}

それぞれ、BattleHandlerクラスを継承し、battleResult()メソッドとgetMessage()メソッドを実装しています。ここでのポイントは、battleResult()メソッドではその判定条件となるアルゴリズムを実装し結果として返している点です。つまりここでは、プレイヤーレベルが各々の敵レベルより高ければこちらの勝ち、低ければコンピュータの勝ちという単純なものです。

実装が完了したので、クライアントから利用してみます。

index.php
<?php
require_once 'autoload.php';

use App\Handlers\RyuBattleHandler;
use App\Handlers\KenBattleHandler;
use App\Handlers\HondaBattleHandler;

$ryu = new RyuBattleHandler();
$ken = new KenBattleHandler();
$honda = new HondaBattleHandler();

$handler = $ryu->setHandler($ken->setHandler($honda));

for ($i=1; $i<=10; $i++) {
$player_level = rand(1, 30);
echo sprintf('プレイ:%d回目 あなたのプレイヤーレベル:%d<br>', $i, $player_level);
echo $handler->battle($player_level);
echo '<br><br>';
}

上記では、全部で10回のプレイ内容を表してしています。ランダムでプレイヤーレベルを設定し、それによって結果を表示する流れになっています。

また、setHandler()でオブジェクトをチェインしています。ここでオブジェクトを繋げるのがChain of Responsibilityパターンの特徴です。これによって、コンピュータプレイヤーとの対戦ストーリーとして、初戦はRyuと(レベル10以上で勝利)、次はKenと(レベル15以上で勝利)、そして最後にHondaと(レベル20以上で勝利)対戦します。全て勝利すればめでたく全クリとなり、負ければその時点でゲームは終了です。

これらを動作させた場合、結果は以下になります。

ChainOfResponsibilityパターンで動作させたプログラム結果

プレイヤーレベルに応じての結果が出力されている事が確認できます。Chain of Responsibilityパターンでオブジェクトを連鎖させ処理が行われている事を確認できました。

まとめ

Chain of Responsibility=責任の連鎖」を意味する通り、このデザインパターンを用いると、ある判定条件で次のオブジェクトへ受け渡し、そしてまた処理判定されていくという、処理の連鎖を構築する事が出来ます。

そしてそれは、処理を取りまわす側と実際に処理を行う側が分離され、1つのクラスの複雑化を防ぎ、見通しの良いコードへと置き換えます。つまりはハンドラです。処理を取りまわす側は、ハンドラの役割に専念できるようになります。

ただしこのパターンは一定以上の規模になると逆に中身の把握が大変になるという一面も持っているので、javaでもPHPでも常にデザインパターンの原点を忘れず、要件と照らし合わせて適切な設計を心がけていく事が大切です。