RitoLabo

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

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

Commandパターン(コマンド・パターン)は、振る舞いに関するデザインパターン手法の1つで、ある機能に於いて処理の対象・要求・管理の関係性を築く事で、柔軟な処理の取り回しを行える処理モデルです。

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

Commandパターンクラス図

実装例

今回は、あるラーメン屋さんが注文をさばく一連の流れをCommandパターンで実装します。

  • 注文を取る
  • 厨房に伝える
  • 料理を作る

大体こんなところでしょうか。

まずはReceiverクラスです。Commandクラスが処理するラーメンオブジェクトを作成します。

App/Receiver/Ramen.php
<?php

namespace App\Receiver;

class Ramen
{
private $name;
private $ramen;

public function setName($name)
{
$this->name = sprintf('%sラーメン', $name);
}

public function getName()
{
return $this->name;
}

public function setRamen($ramen)
{
$this->ramen = $ramen;
}

public function getRamen()
{
return $this->ramen;
}

public function call()
{
echo sprintf('%s できたよー!<br>', $this->getName());
}
}

料理の名前とデータのゲッタセッタ、そして処理後の呼びかけを行うcall()メソッドを定義しています。

次に、Commandクラスです。インターフェイスを定義します。

App/Command/Interfaces/CommandInterface.php
<?php

namespace App\Command\Interfaces;

interface CommandInterface
{
public function execute();
}

具体的な命令を実装するCommand具象クラスの共通処理として execute() メソッドを定義します。

次はCommand具象クラスです。ラーメンなので「醤油」「味噌」「塩」「豚骨」のクラスを作成します。

App/Command/ShouyuCommand.php
<?php

namespace App\Command;

use App\Command\Interfaces\CommandInterface;
use App\Receiver\Ramen;

class ShouyuCommand implements CommandInterface
{
private $category = 'しょうゆ';

private $ramen;

public function __construct(Ramen $ramen)
{
$this->ramen = $ramen;
$this->ramen->setName($this->category);
}

public function execute()
{
/*
* 醤油ラーメンを作る処理
*/

$this->ramen->call();
}
}
App/Command/MisoCommand.php
<?php

namespace App\Command;

use App\Command\Interfaces\CommandInterface;
use App\Receiver\Ramen;

class MisoCommand implements CommandInterface
{
private $category = 'みそ';

private $ramen;

public function __construct(Ramen $ramen)
{
$this->ramen = $ramen;
$this->ramen->setName($this->category);
}

public function execute()
{
/*
* 味噌ラーメンを作る処理
*/

$this->ramen->call();
}
}
App/Command/ShioCommand.php
<?php

namespace App\Command;

use App\Command\Interfaces\CommandInterface;
use App\Receiver\Ramen;

class ShioCommand implements CommandInterface
{
private $category = 'しお';

private $ramen;

public function __construct(Ramen $ramen)
{
$this->ramen = $ramen;
$this->ramen->setName($this->category);
}

public function execute()
{
/*
* 塩ラーメンを作る処理
*/

$this->ramen->call();
}
}
App/Command/TonkotsuCommand.php
<?php

namespace App\Command;

use App\Command\Interfaces\CommandInterface;
use App\Receiver\Ramen;

class TonkotsuCommand implements CommandInterface
{
private $category = 'とんこつ';

private $ramen;

public function __construct(Ramen $ramen)
{
$this->ramen = $ramen;
$this->ramen->setName($this->category);
}

public function execute()
{
/*
* とんこつラーメンを作る処理
*/

$this->ramen->call();
}
}

コンストラクタでReceiverオブジェクトを受け取り、命令に関する処理であるインターフェイスを実装しています。今回は特に処理は行わず、処理完了を示すcall()メソッドだけを呼んでいます。

最後に、Invokerクラスです。このクラスは命令を保持し、処理を実行します。

App/Invoker/Queue.php
<?php

namespace App\Invoker;

use App\Command\Interfaces\CommandInterface AS Command;

class Queue
{
private $commands;

public function __construct()
{
$this->commands = [];
}
public function addCommand(Command $command)
{
$this->commands[] = $command;
}

public function run()
{
if (!empty($this->commands)) {
foreach ($this->commands as $command) {
$command->execute();
}
$this->commands = [];
}
}
}

addCommand() メソッドで命令を追加し、run() メソッドでそれらを順番に実行していきます。

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

index.php
<?php

use App\Invoker\Queue;
use App\Receiver\Ramen;
use App\Command\ShouyuCommand;
use App\Command\MisoCommand;
use App\Command\ShioCommand;
use App\Command\TonkotsuCommand;

$queue = new Queue();

// 4人分のオーダーです。
$queue->addCommand(new ShouyuCommand(new Ramen()));
$queue->addCommand(new MisoCommand(new Ramen()));
$queue->addCommand(new ShioCommand(new Ramen()));
$queue->addCommand(new TonkotsuCommand(new Ramen()));

$queue->run();

echo '<hr>';

// 次は、5人分のオーダーです。
$queue->addCommand(new TonkotsuCommand(new Ramen()));
$queue->addCommand(new MisoCommand(new Ramen()));
$queue->addCommand(new TonkotsuCommand(new Ramen()));
$queue->addCommand(new ShouyuCommand(new Ramen()));
$queue->addCommand(new ShioCommand(new Ramen()));

$queue->run();

Receiverを吸収したCommandをInvokerの中へ登録し、最後にInvokerがrun()メソッドで命令を全て実行します。

結果は以下になります。

PHPでCommandパターン実行結果

Commandパターンを用いて要求の送受信と処理実行のロジックを分離出来た事を確認できました。

まとめ

「Command = 命令」を意味する通り、処理の要求自体をオブジェクト化し処理対象のオブジェクトとの間に要求を管理・実行するオブジェクトを挟む事で、処理の追加や拡張を容易にしたり、処理の順番や結果自体を管理できている事で、キューやワーカーのような使い方や、処理の巻き戻しといった事も行えるようになります。

設計次第で色々な使い方が出来るので、是非試してみてください。