RitoLabo

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

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

AbstractFactoryパターン(アブストラクト・ファクトリ・パターン)は「抽象的な工場」を意味する通り、関係するオブジェクトたちを”まとめて”生成する為のデザインパターン手法の一つです。

生成に関するデザインパターンの1つとして「FactoryMethodパターン」がありますが、これはオブジェクトの生成についての抽象化をメインとしてパターンであるのに対し、AbstractFactoryパターンは関連オブジェクトをまとめて生成する流れに関しての抽象化になります。

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

AbstractFactoryパターンクラス図

FactoryでProductのパターンを宣言し、Productで細分化されたItemを取りまわして作成していく。

この流れの中で、FactoryとProductには共通のルールを持たせる(抽象化)事で、それぞれのFactoryのバリエーション(処理ロジックの切り替えレベル)、そしてProductのパターン(生成するオブジェクトの種類レベル)に関して処理の流れを共通化できます。

こうするとあとはFactoryを選ぶだけで処理を切り替える事ができる。これが簡単なAbstractFactoryパターンの構造になります。

実装例

今回は、繁忙期の自動車工場の生産ラインを例にしたいと思います。

PHP自動車株式会社の工場では、2つの人気車種「Laravel」と「cakephp」の生産がピークです。あまりの人気で生産が追い付かず、工場では24時間稼働にし、時間で生産を切り替えていく事にしました。

ただし、同じ生産ラインで時間によって生産するものを変えるというのにはとてもリスクが伴います。部品などを全て総取り換えしなければなりませんし、部品の切り替えに不備がありLaravelを組み立てる時にcakephpの部品が入ってしまったらリコールで大損害です。

そこで、工場長はAbstractFactoryパターンで生産ラインを回す事にしました。

まず前提として、それぞれの車種について全て別々の部品が使われており、それぞれの部品リストはjsonファイルに収録されている事とします。

まずは工場(Factory)で生産するものをインターフェイスに定義します。

App/Cars/Factories/Interfaces/FactoryInterface.php
<?php

namespace App\Cars\Factories\Interfaces;

interface FactoryInterface
{
public function engine();
public function tire();
public function handle();
}

この工場では、エンジン/ハンドル/タイヤの取り付けを行っているので、それらを定義しています。

次に、このインターフェイスを実装し、車種「Laravel」のFactoryクラスを実装します。

App/Cars/Factories/LaravelFactory.php
<?php

namespace App\Cars\Factories;

use App\Cars\Factories\Interfaces\FactoryInterface;
use App\Cars\Products\Laravel\LaravelEngine;
use App\Cars\Products\Laravel\LaravelTire;
use App\Cars\Products\Laravel\LaravelHandle;

class LaravelFactory implements FactoryInterface
{
public function engine()
{
return new LaravelEngine();
}

public function tire()
{
return new LaravelTire();
}

public function handle()
{
return new LaravelHandle();
}

}

ここでは、エンジン・タイヤ・ハンドルについてのProductクラスを生成し返却しています。

そしてProductクラスを実装します。まずはインターフェイスから

App/Cars/Products/Interfaces/EngineInterface.php
<?php

namespace App\Cars\Products\Interfaces;

interface EngineInterface
{
public function partList();
public function assembly();
public function add();
}
App/Cars/Products/Interfaces/TireInterface.php
<?php

namespace App\Cars\Products\Interfaces;

interface TireInterface
{
public function partList();
public function assembly();
public function add();
}
App/Cars/Products/Interfaces/HandleInterface.php
<?php

namespace App\Cars\Products\Interfaces;

interface HandleInterface
{
public function partList();
public function assembly();
public function add();
}
  • 部品のリストに関する partList() メソッド
  • 組み立てに関する assembly() メソッド
  • 取付に関する add() メソッド

そしてProductの具象クラスは以下になります。

App/Cars/Products/Laravel/LaravelEngine.php
<?php

namespace App\Cars\Products\Laravel;

use App\Cars\Products\Interfaces\EngineInterface;
use App\Cars\Items\Laravel\EngineItem;

class LaravelEngine implements EngineInterface
{
protected $json;

public function __construct()
{
$this->json = 'App/files/engine_parts.json';
}

public function partList()
{
$part_map = json_decode(file_get_contents($this->json));

$parts_list = [];
foreach ($part_map as $parts) {
if ($parts->model === "Laravel") {
$parts_list[] = new EngineItem($parts->id, $parts->name, $parts->model);
}
}
return $parts_list;
}

public function assembly()
{
$list = "";
foreach ($this->partList() as $parts) {
$list .= sprintf(
"<li>Parts-No.%d %s | Target Model - %s</li>",
$parts->getId(), $parts->getName(), $parts->getModel()
);
}
return $list;
}

public function add()
{
echo "<h2>Engine</h2>";
echo "<ul>";
echo $this->assembly();
echo "</ul>";
}
}
App/Cars/Products/Laravel/LaravelTire.php
<?php

namespace App\Cars\Products\Laravel;

use App\Cars\Products\Interfaces\EngineInterface;
use App\Cars\Items\Laravel\HandleItem;

class LaravelHandle implements EngineInterface
{
protected $json;

public function __construct()
{
$this->json = 'App/files/handle_parts.json';
}

public function partList()
{
$part_map = json_decode(file_get_contents($this->json));

$parts_list = [];
foreach ($part_map as $parts) {
if ($parts->model === "Laravel") {
$parts_list[] = new HandleItem($parts->id, $parts->name, $parts->model);
}
}
return $parts_list;
}

public function assembly()
{
$list = "";
foreach ($this->partList() as $parts) {
$list .= sprintf(
"<li>Parts-No.%d %s | Target Model - %s</li>",
$parts->getId(), $parts->getName(), $parts->getModel()
);
}
return $list;
}

public function add()
{
echo "<h2>Handle</h2>";
echo "<ul>";
echo $this->assembly();
echo "</ul>";
}

}
App/Cars/Products/Laravel/LaravelHandle.php
<?php

namespace App\Cars\Products\Laravel;

use App\Cars\Products\Interfaces\EngineInterface;
use App\Cars\Items\Laravel\TireItem;

class LaravelTire implements EngineInterface
{
protected $json;

public function __construct()
{
$this->json = 'App/files/tire_parts.json';
}

public function partList()
{
$part_map = json_decode(file_get_contents($this->json));

$parts_list = [];
foreach ($part_map as $parts) {
if ($parts->model === "Laravel") {
$parts_list[] = new TireItem($parts->id, $parts->name, $parts->model);
}
}
return $parts_list;
}

public function assembly()
{
$list = "";
foreach ($this->partList() as $parts) {
$list .= sprintf(
"<li>Parts-No.%d %s | Target Model - %s</li>",
$parts->getId(), $parts->getName(), $parts->getModel()
);
}
return $list;
}

public function add()
{
echo "<h2>Tire</h2>";
echo "<ul>";
echo $this->assembly();
echo "</ul>";
}
}

最後に、それぞれの部品を定義している各Itemクラスです。

App/Cars/Items/Laravel/EngineItem.php
<?php

namespace App\Cars\Items\Laravel;

class EngineItem
{
private $id;
private $name;
private $model;

public function __construct($id, $name, $model)
{
$this->id = $id;
$this->name = $name;
$this->model = $model;
}
public function getId()
{
return $this->id;
}
public function getName()
{
return $this->name;
}
public function getModel()
{
return $this->model;
}
}
App/Cars/Items/Laravel/TireItem.php
<?php

namespace App\Cars\Items\Laravel;

class TireItem
{
private $id;
private $name;
private $model;

public function __construct($id, $name, $model)
{
$this->id = $id;
$this->name = $name;
$this->model = $model;
}
public function getId()
{
return $this->id;
}
public function getName()
{
return $this->name;
}
public function getModel()
{
return $this->model;
}
}
App/Cars/Items/Laravel/HandleItem.php
<?php

namespace App\Cars\Items\Laravel;

class HandleItem
{
private $id;
private $name;
private $model;

public function __construct($id, $name, $model)
{
$this->id = $id;
$this->name = $name;
$this->model = $model;
}
public function getId()
{
return $this->id;
}
public function getName()
{
return $this->name;
}
public function getModel()
{
return $this->model;
}
}

これで車種「Laravel」側の一連の実装が完了しました。同じ要領でもう一方の車種「cakephp」も実装し、これらを用いて生産ラインを動かしてみます。

index.php
<?php

use App\Cars\Factories\LaravelFactory;
use App\Cars\Factories\CakephpFactory;

$car_models = [
1 => "laravel",
2 => "cakephp"
];

$target = $car_models[rand(1,2)];

if ($target == "laravel") {
$model = new LaravelFactory();
} else if ($target == "cakephp") {
$model = new CakephpFactory();
}

echo sprintf("<h1>Car Model%s</h1>", $target);

$engine = $model->engine();
$engine->add();

$tire = $model->tire();
$tire->add();

$handle = $model->handle();
$handle->add();

作る車種でFactoryクラスを切り替える事によって、それぞれの部門の結果を表示しています。結果は以下になります。

車種Laravelの結果表示

車種Laravel結果

車種cakephpの結果表示

車種cakephp結果

このように、Factoryクラスを切り替える事によって、関連するオブジェクトをまとめて生成し提供できる事が確認できました。

まとめ

クラス図や実装例だけではイメージが付きづらいと思いますが、それぞれの関係性について以下のように記すとよりイメージもつきやすいです。

index
- aConcreteFactory imprements AbstractFactoryInterface
- aConcreteProduct01 imprements Product01Interface
- Item01
- aConcreteProduct02 imprements Product02Interface
- Item02

- bConcreteFactory imprements AbstractFactoryInterface
- bConcreteProduct01 imprements Product01Interface
- Item03
- bConcreteProduct02 imprements Product02Interface
- Item04

AbstractFactoryパターンを用いると、オブジェクトをグループ単位で入れ替えたり追加したりが行えるので便利です。是非試してみてください。