1. Home
  2. PHP
  3. DesignPatterns
  4. FactoryMethodパターン | PHPデザインパターン

FactoryMethodパターン | PHPデザインパターン

  • 公開日
  • 更新日
  • カテゴリ:DesignPatterns
  • タグ:PHP,Factory,DesignPatterns,Creation,FactoryMethod
FactoryMethodパターン | PHPデザインパターン

FactoryMethod パターンは、オブジェクト生成方法に関するデザインパターンの手法の1つです。

それぞれ必要なオブジェクト生成の為のインターフェイスを用意しておき、どのクラスをインスタンス化するかを Factory クラスで決定し供給するようにする。というのが大まかな流れになります。

FactoryMethod パターンのクラス図は以下になります。

例えば、今回はあるフォーマットのデータを処理する機能を実装するとします。

その機能は単純にデータを受け取り内容を表示するものですが、受け取るデータのフォーマットが何通りかあったり、まだ固まっていなかったりしています。

そこで、どんなフォーマットのデータでも柔軟に対応できるように機能を実装する必要があります。

この「柔軟に」というのが、FactoryMethod パターンを用いる最大の利点となります。

具体的には、違うフォーマットのデータが来た場合でもそこの変換だけを追加すれば済むように作れば、コア部分は変えずに済む。というロジックになります。

実装例

都道府県を表示処理する例を考えます。データの内容としては、「地方名」そして「都道府県名」の2つがありますが、データのフォーマットとしてそれらが一緒に来るパターンと、別々に来るパターンがあるとします。これらの違いを吸収して、どんなフォーマットのデータが来たとしても対応できる表示処理機能を実装します。

まずはデータです。以下の3つのデータファイルがあるとします。

  • 地方名を収録した region.json
  • 都道府県名を収録した prefectures.json
  • 地方名と都道府県名を一緒に収録した japan.json

そしてこららのファイルは、以下のようなデータを収録しています。(json を配列に修正して表示しています)

region.json
[
    ["id" => "00001", "name" =>"北海道地方"],
    ["id" => "00002", "name" =>"東北地方"],
    ["id" => "00003", "name" =>"関東地方"],
    ["id" => "00004", "name" =>"中部地方"],
    ["id" => "00005", "name" =>"近畿地方"],
    ["id" => "00006", "name" =>"中国地方"],
    ["id" => "00007", "name" =>"四国地方"],
    ["id" => "00008", "name" =>"九州地方"],
];
prefectures.json
[
  "00001" => [
    ["id" => "01", "name" =>"北海道"],
  ],
  "00002" => [
    ["id" => "02", "name" =>"青森県"],
    ["id" => "03", "name" =>"岩手県"],
    ["id" => "04", "name" =>"宮城県"],
    ["id" => "05", "name" =>"秋田県"],
    ["id" => "06", "name" =>"山形県"],
    ["id" => "07", "name" =>"福島県"],
  ],
  "00003" => [
    ["id" => "08", "name" =>"茨城県"],
    ["id" => "09", "name" =>"栃木県"],
    ["id" => "10", "name" =>"群馬県"],
    ["id" => "11", "name" =>"埼玉県"],
    ["id" => "12", "name" =>"千葉県"],
    ["id" => "13", "name" =>"東京都"],
    ["id" => "14", "name" =>"神奈川県"],
  ],
  "00004" => [
    ["id" => "15", "name" =>"新潟県"],
    ["id" => "16", "name" =>"富山県"],
    ["id" => "17", "name" =>"石川県"],
    ["id" => "18", "name" =>"福井県"],
    ["id" => "19", "name" =>"山梨県"],
    ["id" => "20", "name" =>"長野県"],
    ["id" => "21", "name" =>"岐阜県"],
    ["id" => "22", "name" =>"静岡県"],
    ["id" => "23", "name" =>"愛知県"],
  ],
  "00005" => [
    ["id" => "24", "name" =>"三重県"],
    ["id" => "25", "name" =>"滋賀県"],
    ["id" => "26", "name" =>"京都府"],
    ["id" => "27", "name" =>"大阪府"],
    ["id" => "28", "name" =>"兵庫県"],
    ["id" => "29", "name" =>"奈良県"],
    ["id" => "30", "name" =>"和歌山県"],
  ],
  "00006" => [
    ["id" => "31", "name" =>"鳥取県"],
    ["id" => "32", "name" =>"島根県"],
    ["id" => "33", "name" =>"岡山県"],
    ["id" => "34", "name" =>"広島県"],
    ["id" => "35", "name" =>"山口県"],
  ],
  "00007" => [
    ["id" => "36", "name" =>"徳島県"],
    ["id" => "37", "name" =>"香川県"],
    ["id" => "38", "name" =>"愛媛県"],
    ["id" => "39", "name" =>"高知県"],
  ],
  "00008" => [
    ["id" => "40", "name" =>"福岡県"],
    ["id" => "41", "name" =>"佐賀県"],
    ["id" => "42", "name" =>"長崎県"],
    ["id" => "43", "name" =>"熊本県"],
    ["id" => "44", "name" =>"大分県"],
    ["id" => "45", "name" =>"宮崎県"],
    ["id" => "46", "name" =>"鹿児島県"],
    ["id" => "47", "name" =>"沖縄県"],
  ],
];

japan.json
[
  [
    "id" => "00001",
    "name" =>"北海道地方",
    "prefecture" => [
      ["id" => "01", "name" =>"北海道"],
    ]
  ],
  [
    "id" => "00002",
    "name" =>"東北地方",
    "prefecture" => [
      ["id" => "02", "name" =>"青森県"],
      ["id" => "03", "name" =>"岩手県"],
      ["id" => "04", "name" =>"宮城県"],
      ["id" => "05", "name" =>"秋田県"],
      ["id" => "06", "name" =>"山形県"],
      ["id" => "07", "name" =>"福島県"],
    ]
  ],
  [
    "id" => "00003",
    "name" =>"関東地方",
    "prefecture" => [
      ["id" => "08", "name" =>"茨城県"],
      ["id" => "09", "name" =>"栃木県"],
      ["id" => "10", "name" =>"群馬県"],
      ["id" => "11", "name" =>"埼玉県"],
      ["id" => "12", "name" =>"千葉県"],
      ["id" => "13", "name" =>"東京都"],
      ["id" => "14", "name" =>"神奈川県"],
    ]
  ],
  [
    "id" => "00004",
    "name" =>"中部地方",
    "prefecture" => [
      ["id" => "15", "name" =>"新潟県"],
      ["id" => "16", "name" =>"富山県"],
      ["id" => "17", "name" =>"石川県"],
      ["id" => "18", "name" =>"福井県"],
      ["id" => "19", "name" =>"山梨県"],
      ["id" => "20", "name" =>"長野県"],
      ["id" => "21", "name" =>"岐阜県"],
      ["id" => "22", "name" =>"静岡県"],
      ["id" => "23", "name" =>"愛知県"],
    ]
  ],
  [
    "id" => "00005",
    "name" =>"近畿地方",
    "prefecture" => [
      ["id" => "24", "name" =>"三重県"],
      ["id" => "25", "name" =>"滋賀県"],
      ["id" => "26", "name" =>"京都府"],
      ["id" => "27", "name" =>"大阪府"],
      ["id" => "28", "name" =>"兵庫県"],
      ["id" => "29", "name" =>"奈良県"],
      ["id" => "30", "name" =>"和歌山県"],
    ]
  ],
  [
    "id" => "00006",
    "name" =>"中国地方",
    "prefecture" => [
      ["id" => "31", "name" =>"鳥取県"],
      ["id" => "32", "name" =>"島根県"],
      ["id" => "33", "name" =>"岡山県"],
      ["id" => "34", "name" =>"広島県"],
      ["id" => "35", "name" =>"山口県"],
    ]
  ],
  [
    "id" => "00007",
    "name" =>"四国地方",
    "prefecture" => [
      ["id" => "36", "name" =>"徳島県"],
      ["id" => "37", "name" =>"香川県"],
      ["id" => "38", "name" =>"愛媛県"],
      ["id" => "39", "name" =>"高知県"],
    ]
  ],
  [
    "id" => "00008",
    "name" =>"九州地方",
    "prefecture" => [
      ["id" => "40", "name" =>"福岡県"],
      ["id" => "41", "name" =>"佐賀県"],
      ["id" => "42", "name" =>"長崎県"],
      ["id" => "43", "name" =>"熊本県"],
      ["id" => "44", "name" =>"大分県"],
      ["id" => "45", "name" =>"宮崎県"],
      ["id" => "46", "name" =>"鹿児島県"],
      ["id" => "47", "name" =>"沖縄県"],
    ]
  ],
];

次に、処理パターンです。対応しなければいけない以下2つのパターンがあるとします。

  • パターン A
    • japan.json が配布され、それを表示処理する
  • パターン B
    • region.json と prefectures.json が配布され、それを表示処理する

これらを FactoryMethod パターンで実装します。

インターフェイス

まずはインターフェイスです。

App/DataReader/Interfaces/DataReaderInterface.php
<?php

namespace App\DataReader\Interfaces;

interface DataReaderInterface
{
    public function convert($json_url);
    public function show();
}

json データ処理クラスの為の共通処理としてインターフェイスを定義しています。 convert() メソッドでは json データの変換を定義し、show() メソッドで取り込んだデータを表示する処理を実装します。

Product クラス

次に、それぞれの json データ処理クラスを定義します。両方とも、先ほど定義したインターフェイスを実装します。

下記はパターン A である1つの json ファイルを処理するクラスです。

App/DataReader/Readers/BulkDataReader.php
<?php

namespace App\DataReader\Readers;

use App\DataReader\Interfaces\DataReaderInterface;

class BulkDataReader implements DataReaderInterface
{
    protected $data;

    public function __construct($json)
    {
        $this->data = $this->convert($json);
    }

    public function convert($json_url)
    {
        return json_decode(file_get_contents($json_url));
    }

    public function show()
    {
        foreach ($this->data as $d) {
            echo $d->name . "<br>";
            foreach ($d->prefecture As $c) {
                echo $c->name . " ";
            }
            echo "<br><br>";
        }
    }
}

下記はパターン B である2つの json ファイルを処理するクラスです。

App/DataReader/Readers/SplitDataReader.php
<?php

namespace App\DataReader\Readers;

use App\DataReader\Interfaces\DataReaderInterface;

class SplitDataReader implements DataReaderInterface
{
    protected $regions;
    protected $prefectures;

    public function __construct($json_array)
    {
        $this->regions = $this->convert($json_array[0]);
        $this->prefecture = $this->convert($json_array[1]);
    }

    public function convert($json_url)
    {
        return json_decode(file_get_contents($json_url));
    }

    public function show()
    {
        foreach ($this->regions as $r) {
            echo $r->name . "<br>";
            $region_id = $r->id;
            foreach ($this->prefecture->$region_id as $p) {
                echo $p->name . ' ';
            }
            echo "<br><br>";
        }
    }
}

Factory クラス

次に Factory クラスを作成します。

App/DataReader/Factories/ReaderFactory.php
<?php

namespace App\DataReader\Factories;

use App\DataReader\Readers\BulkDataReader;
use App\DataReader\Readers\SplitDataReader;

class ReaderFactory
{
    public function create($json)
    {
        return $this->createReader($json);
    }

    public function createReader($json)
    {
        if(is_array($json)) {
            return new SplitDataReader($json);
        } else {
            return new BulkDataReader($json);
        }
    }
}

渡ってくる json 情報によって生成するオブジェクトを決定しています。 Factory クラスはその名の通り「工場=生成」の役割を持っています。

Client

最後に、これらを使うクライアント部分です。

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

use App\DataReader\Factories\ReaderFactory;

$factory = new ReaderFactory();

// パターンA 1つのjsonファイルを処理する
$json = 'App/files/japan.json';
$pattern_a = $factory->create($json);
$pattern_a->show();

echo "<hr>";

// パターンB 2つのjsonファイルを処理する
$json_array[] = 'App/files/region.json';
$json_array[] = 'App/files/prefectures.json';
$pattern_b = $factory->create($json_array);
$pattern_b->show();

ブラウザからアクセスして表示させてみると、パターン A とパターン B に関して、渡すデータが違うだけで、それ以外は同じ記述で同じ表示結果が得られている事を確認できます。

今回の実装例をクラス図に起こすと以下のようになります。

まとめ

FactoryMethod パターンはオブジェクトの生成方法に係る手法なので、例えばファイル形式の違うものの生成分岐であったり、その他でも様々な状況下で使用できます。

開発では似たような状況下での実装は少なくありませんが、規模によっては手数も限られているので後先考えずにとりあえずで実装してしまう事も少なくはありません。ですが、このパターンを知っているのといないのでは、その後のリペアにかなりの違いが出るので、是非試してみてください。

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級