1. Home
  2. PHP
  3. DesignPatterns
  4. AbstractFactoryパターン - PHPデザインパターン

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

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

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

生成に関するデザインパターンの1つとして「FactoryMethod パターン」がありますが、これはオブジェクトの生成についての抽象化をメインとしてパターンであるのに対し、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 の結果表示

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

Author

rito

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