RitoLabo

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

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

Stateパターン(ステート・パターン)は、は振る舞いに関するデザインパターン手法の一つで、1つの状態を1つのオブジェクトとして表現し、状態の変更をシンプルに構築できる処理モデルです。

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

Stateパターンクラス図

実装例

今回は、ユーザのオンライン・オフラインの状態を切り替える機能をStateパターンで実装し、その流れを見ていきます。

App/State/Interfaces/StateInterface.php
<?php

namespace App\State\Interfaces;

interface StateInterface
{
public function nextState();
public function getStatus();
}

状態を切り替えるnextState() メソッドと、状態を返すgetStatus()メソッドを用意しました。

次に、Stateの具象クラスです。先ほど作成したインターフェイスを実装し、オンライン状態とオフライン状態の2つのクラスを定義します。

App/State/OnlineState.php
<?php

namespace App\State;

use App\State\Interfaces\StateInterface;

class OnlineState implements StateInterface
{
private static $instance = null;

public static function getInstance()
{
if (empty(self::$instance)) {
self::$instance = new OnlineState();
}
return self::$instance;
}

public function nextState()
{
return OfflineState::getInstance();
}

public function getStatus()
{
return '<span style="color: blue;">オンライン</span>';
}

public final function __clone()
{
throw new \Exception('This Instance is Not Clone');
}

public final function __wakeup()
{
throw new \Exception('This Instance is Not unserialize');
}
}
App/State/OfflineState.php
<?php

namespace App\State;

use App\State\Interfaces\StateInterface;

class OfflineState implements StateInterface
{
private static $instance = null;

public static function getInstance()
{
if (empty(self::$instance)) {
self::$instance = new OfflineState();
}
return self::$instance;
}

public function nextState()
{
return OnlineState::getInstance();
}

public function getStatus()
{
return '<span style="color: darkgray;">オフライン</span>';
}

public final function __clone()
{
throw new \Exception('This Instance is Not Clone');
}

public final function __wakeup()
{
throw new \Exception('This Instance is Not unserialize');
}
}

状態を表現するStateクラスはSingletonパターンを用いて作成します(1つの状態を表すオブジェクトは複数不要で1つあればよい)。そして、nextState() メソッドで切り替えるべき次の状態を渡しています。

次に、Contextクラスです。今回はユーザとして定義していきます。

App/Context/User.php
<?php

namespace App\Context;

use App\State\OnlineState;

class User
{
private $name;
private $state;

public function __construct($name)
{
$this->name = $name;
$this->state = OnlineState::getInstance();
}

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

public function changeState()
{
$this->state = $this->state->nextState();
}

public function getStatus()
{
return $this->state->getStatus();
}
}
  • コンストラクタでは、初期の状態インスタンスをセットしています。
  • changeState() メソッドで次の状態へ変更しています。ユーザーは自身の処理ではなく、所持しているstateクラスの変更メソッドをただ実行しているだけです。

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

index.php
<?php

use App\Context\User;

$user = new User('rito');

echo sprintf('ユーザ:%s 状態:%s<br><br>', $user->getName(), $user->getStatus());

// 状態を変更する
$user->changeState();

echo sprintf('ユーザ:%s 状態:%s<br><br>', $user->getName(), $user->getStatus());

// 状態を変更する
$user->changeState();

echo sprintf('ユーザ:%s 状態:%s<br><br>', $user->getName(), $user->getStatus());

// 状態を変更する
$user->changeState();

echo sprintf('ユーザ:%s 状態:%s<br><br>', $user->getName(), $user->getStatus());

ユーザーのインスタンスを生成し、状態の切り替えを何度か行っています。見ての通り、現在のユーザーの状態を気にする事なく、changeState()メソッドを実行し状態の切り替えを行う事が可能であり、これがStateパターンを用いる利点です。

実行結果は以下になります。

Stateパターン実行結果

Stateパターンで状態切り替えを実装出来た事が確認できました。

まとめ

State = 状態」を意味する通り、Stateパターンではその「もの」ではなく、その「状態」をクラスとして1つの機能を構築します。Stateパターンを用いる事で、その「状態」の変化によって振る舞いを変更する事が出来るようになり、例えば状態の変更の為に条件分岐に頼らなくても良くなります。

State具象クラスにはSingletonパターンを採用し、状態の切り替えはContextクラスではなくあくまでもState具象クラスで行うなど一定のルールもありますが、わりと使いやすいパターンなので是非試してみてください。