RitoLabo

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

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

Builderパターンはオブジェクトの生成方法に関する処理モデルです。オブジェクト生成の流れを抽象化する(生成の手順や手段を切り離す)事で、オブジェクトの生成を柔軟にします。

Builderという言葉自体が「建築者」「建造者」を指している通り、このパターンにはDirector(ディレクター)とBuilder(ビルダー)という関係が構築されていて、管理実行・処理手法・扱う素材がそれぞれ独立しています。

例えば、監督(ディレクター)が、職人(ビルダー)と扱う素材の2つを以て家具の制作指示を出す事で、職人はせっせとその素材を加工し、返します。この時、職人は今作っている家具がどこの家のどの部屋のものなのかを知りませんし、知る必要がありません。この疎結合な関係は、変更(差し替えなど)も影響範囲が少なく容易に行う事が出来ます。

簡単な話、Builderパターンを実装すると、処理の流れを変えずに職人と素材だけを変更すれば、それで様々なものが作れるようになります。

この「保持する」「処理する」「対象のデータ」分離させる事によって、柔軟な処理導線を構築するのがBuilderパターンの特徴です。

Builderパターンの基本的なクラス図は以下の通りです。

Builderパターンのクラス図

実装例

PHPフレームワークなどを収録したデータを処理する流れをBuilderパターンで実装します。

まずは、Productクラスです。データ「Frameworks」の定義を行います。

App/Frameworks/Frameworks.php
<?php

namespace App\Frameworks;


class Frameworks
{
private $id;
private $name;
private $category;

public function __construct($id, $name, $category)
{
$this->id = $id;
$this->name = $name;
$this->category = $category;
}

public function getId()
{
return $this->id;
}

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

public function getCategory()
{
return $this->category;
}

}

基本的な定義のみを行っています。次に、Directorクラスです。

App/Director/FrameworksDirector.php
<?php

namespace App\Director;

use App\Builder\FrameworksBuilder;

/**
* Director Class
*/
class FrameworksDirector
{
private $builder;
private $json;

public function __construct(FrameworksBuilder $builder, $json)
{
$this->builder = $builder;
$this->json = $json;
}

public function getFrameworks()
{
$list = $this->builder->parse($this->json);
return $list;
}
}

監督らしく、コンストラクタで職人と素材を受け取り、getFrameworks()メソッドで結果データを取得しています。

さらに後述しますが、parse()メソッドはインターフェイスで定義された、いわゆる「約束されたメソッド」なので、彼は制作グループの状況など気にしなくても、目をつぶっていても成果物を要求できます。

「スケジュール通りに完璧なものが取得できれば制作過程なんてどうでもいい!早く完成品をよこせ!」

鬼軍曹のようなクラス定義です。でも疎結合と柔軟性の為です。そう割り切るしかありません。

次はBuilderクラスです。インターフェイスを定義します。

App/Builder/Interfaces/FrameworksBuilderInterface.php
<?php

namespace App\Builder\Interfaces;

/**
* Builder Class
*/
interface FrameworksBuilderInterface {
public function parse($data);
}

堅牢な関係構築の為に「約束された地」としてこのparse()メソッドを定義しています。このおかげで、あの鬼監督は昼夜問わずに制作チームへ成果物を要求する事が出来ます。

最後にこのインターフェイスを実装したConcreteBuilderクラスです。つまり、彼らがデータの処理を担います。

App/Builder/FrameworksBuilder.php
<?php

namespace App\Builder;

use App\Frameworks\Frameworks;
use App\Builder\Interfaces\FrameworksBuilderInterface;

/**
* ConcreteBuilder Class
*/
class FrameworksBuilder implements FrameworksBuilderInterface
{
public function parse($json_path)
{
if (empty($json_path) || !file_exists($json_path)) {
throw new Exception('ファイルがありませんでした');
}

$data = $this->convert($json_path);

$list = array();
foreach ($data as $d) {
$list[] = new Frameworks(
$d->id,
$d->name,
$d->category
);
}

return $list;
}

private function convert($json_path)
{
return json_decode(file_get_contents($json_path));
}
}

もちろんparse()メソッドは必須です。鬼監督からガンガン投げられてくる素材(jsonファイルパス)を必死に読み込み・処理をして返す健気な役回りです。

ちなみに今回はjsonファイルを処理する感じにしたので、それを読み込むconvert()メソッドも定義しています。

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

<?php

use App\Director\FrameworksDirector;
use App\Builder\FrameworksBuilder;

$builder = new FrameworksBuilder();
$json_path = 'App/files/frameworks.json';

$director = new FrameworksDirector($builder, $json_path);

$data = $director->getFrameworks();

foreach ($data as $d) {
echo sprintf(
'<li>%s%s [%s]</li>',
$d->getId(),
$d->getName(),
$d->getCategory()
);
}
  1. ビルダーインスタンスを生成
  2. 素材の確定
  3. ビルダーインスタンスと素材をディレクターインスタンス生成と共にに渡す
  4. ディレクターより成果物の要求
  5. 取得された成果物を表示

結果は以下になります。

1:Laravel [PHP]
2:Symfony [PHP]
3:CakePHP [PHP]
4:zend framework [PHP]
5:codeigniter [PHP]
6:Phalcon [PHP]
7:FuelPHP [PHP]
8:Yii [PHP]
9:Slim [PHP]
10:Silex [PHP]
11:Flight [PHP]
12:BEAR.Sunday [PHP]
13:Ethna [PHP]
14:React [Javascript]
15:AngularJS [Javascript]
16:Vue.js [Javascript]
17:Backbone.js [Javascript]
18:jQuery [Javascript]
19:Knockout.js [Javascript]
20:Django [Python]
21:Bottle [Python]
22:Flask [Python]
23:Tornado [Python]

まとめ

それぞれの役割の分離が明確でコードも追いやすく、使いやすいのもBuilderパターンの特徴です。例は例に過ぎず、たくさんの場面でこのパターンを用いる事が出来るので、是非試してみてください。