Help us understand the problem. What is going on with this article?

State パターンとFactory Method パターンでミズゴロウを実装 - PHP -

More than 1 year has passed since last update.

はじめのはじめに

PHPerのみなさんこんにちは、PHP Advent Calendar 2016の14日目のエントリです。

はじめに

Trait という単語を聞いたことあったが、実際勉強したことなかったので。。。やろう・・・っと思ってポケモンを実装していたらなぜかStateとFactory Method パターンの勉強になっちゃった。。。
ということで、PHP で State パターンと Factory Method パターンを実装した話です。
コードやクラス図で間違っているところがありましたら教えてくださるとうれしく思います。

デザインパターンとは

Wikipedia さんより・・・

ソフトウェア開発におけるデザインパターン(型紙(かたがみ)または設計パターン、英: design pattern)とは、過去のソフトウェア設計者が発見し編み出した設計ノウハウを蓄積し、名前をつけ、再利用しやすいように特定の規約に従ってカタログ化したものである。

要するにデザインパターンは実装する上で、再利用しやすいパターンのことです。プログラミングのプロの方々は、知らず知らずのうちに利用していることもあると思います。

GoF

GoF ( Gang of Four; 四人組 ) の略で、 エリック・ガンマ、リチャード・ヘルム、ラルフ・ジョンソン、ジョン・ブリシディース の4名である。また、4名がが共著した「オブジェクト指向における再利用のためのデザインパターン」からデザインパターンがソフトウェア開発で初めて出現した。

State パターン

State パターンはあるオブジェクトの状態を表現するために用いられます。例を挙げると、信号機のの状態遷移などが実装例として挙げられる。
State パターンをクラス図を示すと以下のような感じになります。

470px-State_Design_Pattern_UML_Class_Diagram.svg.png

画像の引用元: Wikipedia「State パターン」より

Factory Method パターン

Factory Method パターンはインスタンスの作り方をスーパークラスで決め、具体的にどのようなインスタンスを生成するのかをサブクラスで行うというものです。また、オブジェクトの生成部分の Creator と処理部分の Product に分けています。
例を言うと、スーパークラスで「自動車を作るクラス」を定義し、サブクラスで、「スポーツカーを作るサブクラス」や「軽自動車を作るサブクラス」を定義してあげるというものです。
クラス図で示すと以下のような感じになります。

500px-Factory_Method_UML_class_diagram.svg.png

画像の引用元: Wikipedia「Factory Method パターン」より

ミズゴロウとは

ポケットモンスタールビー・サファイアで登場したポケモンです。基本データを以下に示します。

項目 ミズゴロウのデータ
図鑑 No.258
分類 ぬまうおポケモン
タイプ みず
たかさ 0.4m
おもさ 7.6kg
とくせい げきりゅう/しめりけ

ミズゴロウは Lv42 でハイドロポンプを覚えます!是非 Lv42 まで進化させずに頑張りましょう。

ミズゴロウの実装

今回実装予定の State パターンと Factory Method パターンを使用したクラス図は以下のようになります。
ALL.png

State パターンの実装

ミズゴロウの進化は、以下の進化順である。

  • ミズゴロウ → ヌマクロー → ラグラージ

これを実際に実装する。
今回の State パターンのクラス図は枠内のようになっている。

State.png

では、実装していきます。

まず初めに PokemonState インターフェースを作成しましょう。

PokemonStateInterface.php
<?php
interface PokemonStateInterface {
    public function evolution();
    public function getName();
}

PokemonStateが、ポケモンの State の型を定義します。それを実装することにより、ポケモンのミズゴロウ、ヌマクロー、ラグラージの State を作成します。では、PokemonStateを実装しましょう。
まず初めにミズゴロウの State です。
evoluation()では、次の遷移先の NumacrawState クラスのインスタンスを返します。getName() では、ポケモンの名前を返すようにしています。(今何のポケモンなのか教えてもらうためです)

MizugorouState.php
<?php
require_once("PokemonStateInterface.php");
require_once("NumacrawState.php");
/**
 * ミズゴロウのState
 */
class MizugorouState implements PokemonStateInterface{
    // ヌマクローに進化
    public function evolution(){
        return new NumacrawState();
    }

    public function getName(){
        return "ミズゴロウ";
    }
}

次は、ヌマクローのState です。
これは MizugorouState クラスと同様に evoluation() は、次の遷移先の LaglargeState クラスのインスタンスを返します。

NumacrawState.php
<?php
require_once("PokemonStateInterface.php");
require_once("LaglargeState.php");
/**
 * ヌマクローのState
 */
class NumacrawState implements PokemonStateInterface{
    // ラグラージに進化
    public function evolution(){
        return new LaglargeState();
    }

    public function getName(){
        return "ヌマクロー";
    }
}

次は、ラグラージのState です。
ラグラージは進化できないため、evoluation() では自身である $this を返すようにしています。

LaglargeState.php
<?php
require_once("PokemonStateInterface.php");
/**
 * ラグラージのState
 */
class LaglargeState implements PokemonStateInterface{
    // ラグラージは進化しないので自身を返却
    public function evolution(){
        return $this;
    }
    //
    public function getName(){
        return "ラグラージ";
    }
}

これらのStateを利用するには、以下のコードで動くと思います。

Mizugorou.php
<?php
require_once("MizugorouState.php");

class Mizugorou{
    private $state = null;
    public function __construct(){
        $this->state = new MizugorouState();
    }
    public function evoluation(){
        $this->state = $this->state->evolution();
    }
    public function showName(){
        echo $this->state->getName()."<br>";
    }
}
$mizugorou = new Mizugorou();
$mizugorou->showName();  // ミズゴロウ

$mizugorou->evoluation();// ヌマクローに進化
$mizugorou->showName();  // ヌマクロー
$mizugorou->evoluation();// ラグラージに進化
$mizugorou->showName();  // ラグラージ
$mizugorou->evoluation();// ラグラージのまま
$mizugorou->showName();  // ラグラージ

Factory Method パターンの実装

今回のFactory Method パターンのクラス図は枠内のようになっている。

Factory.png

Factory Method パターンでは、オブジェクトの生成部分の Creator と処理部分の Product に分けています。

では、まず初めにポケモンの具体的な処理部分( オブジェクトの処理部分の Product )を実装していきましょう。
ポケモンが共通の処理をPokemonProductAbstract クラスに記述する。
今回のポケモンの共通処理は以下のようにした。
- 進化
- 名前の表示
- 作成日時

$pokemonState 変数には、現在のポケモンの状態(State)を保持します。evolution() をすると、次の状態を $pokemonState 変数に代入され、状態遷移を実現しています。

PokemonProductAbstract.php
<?php
/**
 * 作成されるポケモンの抽象クラス
 */
abstract class PokemonProductAbstract{
    // ポケモンの状態を保持するクラス変数
    protected $pokemonState = null;

    // 進化
    public function evolution(){
        $this->pokemonState = $this->pokemonState->evolution();
    }
    // 現在のポケモンの名前取得
    public function showName(){
        echo $this->pokemonState->getName()."<br>";
    }
    // 生成日時を返す関数
    public abstract function showCreationDate();
}

では、これを継承してミズゴロウの処理を MizugorouProduct クラスに書きましょう。
constructorで、MizugorouState のインスタンスを生成することで状態遷移の中のミズゴロウから始まるようにしています。また、スーパークラスで実装されていなかった showCreationDate メソッドを実装しています。

MizugorouProduct.php
<?php
require_once("PokemonProductAbstract.php");
require_once("MizugorouState.php");

/**
 * 工場から作成されるミズゴロウのクラス
 */
class MizugorouProduct extends PokemonProductAbstract
{
    private $creationDate = null;
    public function __construct(){
        // ミズゴロウの状態の生成
        $this->pokemonState = new MizugorouState();
        // 生成日時の取得
        $date = new DateTime();
        $this->creationDate = $date->format('Y-m-d H:i:s');
    }
    // 生成日時を返す関数
    public function showCreationDate(){
        echo $this->creationDate;
    }
}

次は、ポケモンの生成部分(オブジェクトの生成部分の Creator )を実装しましょう。
ポケモンの共通の処理を PokemonCreatorAbstract クラスに記述します。
ここでは、ポケモンを作成する createPokemon() を呼ぶ create() メソッドを記述します。createPokemon() メソッドは、サブクラスで実装を任せるようにしています。
今回であれば、MizugorouCreator クラスがミズゴロウのインスタンスを返す createPokemon() メソッドを実装します。

PokemonCreatorAbstract.php
<?php
/**
 * ポケモンを作成する工場
 */
abstract class PokemonCreatorAbstract{
    // 継承先のcreatePokemon関数を実行(ポケモンのインスタンスを返却)
    public function create(){
        return $this->createPokemon();
    }
    // ポケモンのインスタンス生成
    protected abstract function createPokemon();
}

では、PokemonCreatorAbstract クラスを継承して、ミズゴロウを生成する MizugorouCreator クラスを作成します。ここでは、スーパークラスで実装を任された createPokemon() の実装を行っています。MizugorouCreator クラスではミズゴロウを大量生産したいので、ミズゴロウ( MizugorouProduct クラス)のインスタンスを返しています。

MizugorouCreator.php
<?php
require_once("PokemonCreatorAbstract.php");
require_once("MizugorouProduct.php");
/**
 * ミズゴロウを作成する工場
 */
class MizugorouCreator extends PokemonCreatorAbstract{
    // ミズゴロウを生成
    protected function createPokemon(){
        return new MizugorouProduct();
    }
}

これで、基本的な実装が終了です!あとは State パターンと Factory Method パターンで実装したものを実行するためのコードを書きましょう。

pokemon.php
<?php
require_once("MizugorouCreator.php");

$mizugorou = new MizugorouCreator();
$firstMizugorou = $mizugorou->create(); // ミズゴロウの生成
$firstMizugorou->showName();  // ミズゴロウ
$firstMizugorou->evolution(); // ヌマクローに進化
$firstMizugorou->showName();  // ヌマクロー
$firstMizugorou->evolution(); // ラグラージに進化
$firstMizugorou->showName();  // ラグラージ
$firstMizugorou->evolution(); // ラグラージの進化先はないので、ラグラージのまま
$firstMizugorou->showName();  // ラグラージ
$firstMizugorou->showCreationDate(); // 生成時間の表示
sleep(3);
echo "<hr>";
$secondMizugorou = $mizugorou->create(); // 2匹目のミズゴロウの生成
$secondMizugorou->showName();  // ミズゴロウ
$secondMizugorou->evolution(); // ヌマクローに進化
$secondMizugorou->showName();  // ヌマクロー
$secondMizugorou->evolution(); // ラグラージに進化
$secondMizugorou->showName();  // ラグラージ
$secondMizugorou->evolution(); // ラグラージの進化先はないので、ラグラージのまま
$secondMizugorou->showName();  // ラグラージ
$secondMizugorou->showCreationDate();

実行結果はこんな感じです。

mizugorou.png

最後に

私は、ミズゴロウが好きです。ヌマクローに進化? そのようなことは私にとって許すまじきことです。
ポケットモンスタールビーを昔やっていたのですが、ミズゴロウを Lv100 にするほどです。
ということで、MizugorouState クラスを書きかえます。

MizugorouState.php
<?php
require_once("PokemonStateInterface.php");
require_once("NumacrawState.php");
/**
 * ミズゴロウのState
 */
class MizugorouState implements PokemonStateInterface{
    // ヌマクローに進化
    public function evolution(){
        //開発者権限でミズゴロウにしかならない
        return $this;
    }

    public function getName(){
        return "ミズゴロウ";
    }
}

これで実行しましょう。

ミズゴロウしかならない.png

私の世界に平和がもたらされました)^o^(

明日は @endam さんが phpstormでの快適な開発について 書いてくれるそうです。皆さまお楽しみに!

参考文献

State パターン

Factory Method パターン

その他

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away