RitoLabo

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

  • 公開:
  • カテゴリ: PHP DesignPatterns
  • タグ: PHP,Factory,DesignPatterns,Creation,FactoryMethod

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

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

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

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パターン・サンプルクラス図

まとめ

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

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