RitoLabo

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

  • 公開:
  • カテゴリ: PHP DesignPatterns
  • タグ: PHP,Facade,DesignPatterns,Structure

Facadeパターン(ファサード・パターン)は、構造に関するデザインパターン手法の一つで、サブシステムをまとめその窓口となるクラスを作成する事で、複雑な一連の処理を意識する事なくシンプルにその機能を利用できる処理モデルです。

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

Facadeパターンクラス図

実装例

今回は、BTOでパソコンのカスタマイズを行う場合を例としてFacadeパターンを実装します。

BTO(Build to Order = ビルド・トゥ・オーダー)と言えば、パーツやスペックを好きなように選んで注文できる便利な購買システムですが、各パーツの情報や組み合わせ、金額計算、注文処理など、裏側では色々な処理が動いており、PHPで実装しても複雑化しやすいパターンの一つとも言えます。

ただでさえ接客に忙しい店長は、このシステムの処理にかかり切りになるわけにはいかないと、これらの処理をFacadeパターンで実装する事にしました。

まずはサブシステム群から。パーツ情報を扱うItemクラスです。

App/Bto/Item.php
<?php

namespace App\Bto;


class Item
{
private $id;
private $name;
private $price;

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

public function getName()
{
return $this->name;
}
public function getPrice()
{
return $this->price;
}
public function display()
{
echo sprintf('%s \\%s<br>', $this->getName(), number_format($this->getPrice()));
}
}

パソコンのBTOなので、基本モデル・CPU・メモリ・HDDあたりのチョイスを設けようと思いますが、それらの情報を共通のオブジェクトで持つためのクラスです。

次は、選択するパーツ情報のクラスです。基本モデル・CPU・メモリ・HDDのクラスを作成します。

App/Bto/MachineModel.php
<?php

namespace App\Bto;

use App\Bto\Item;

class MachineModel
{
private $items;

public function __construct()
{
$this->items = [
1 => new Item(1, 'ミニタワー', 10000),
2 => new Item(2, 'ミドルタワー', 20000),
3 => new Item(3, 'フルタワー', 30000)
];
}

public function getItem($id)
{
return $this->items[$id];
}
}
App/Bto/PartsCpu.php
<?php

namespace App\Bto;


class PartsCpu
{
private $items;

public function __construct()
{
$this->items = [
1 => new Item(1, 'Core i3 プロセッサー', 10000),
2 => new Item(2, 'Core i5 プロセッサー', 20000),
3 => new Item(3, 'Core i7 プロセッサー', 30000)
];
}
public function getItem($id)
{
return $this->items[$id];
}
}
App/Bto/PartsMemory.php
<?php

namespace App\Bto;


class PartsMemory
{
private $items;

public function __construct()
{
$this->items = [
1 => new Item(1, '4MB', 10000),
2 => new Item(2, '8MB', 20000),
3 => new Item(3, '16MB', 30000)
];
}
public function getItem($id)
{
return $this->items[$id];
}
}
App/Bto/PartsHdd.php
<?php

namespace App\Bto;


class PartsHdd
{
private $items;

public function __construct()
{
$this->items = [
1 => new Item(1, 'SSD 128GB', 10000),
2 => new Item(2, 'SSD 256GB', 20000),
3 => new Item(3, 'SSD 500GB', 30000)
];
}
public function getItem($id)
{
return $this->items[$id];
}
}

中身はシンプルな感じですが、これらはデータベースから情報を取得するイメージのクラスにしています。(実際はデモなのでベタ書き)

次は、金額計算を行うクラスです。選択したパーツの合計金額を処理します。

App/Bto/TotalFee.php
<?php

namespace App\Bto;


class TotalFee
{
private $total = 0;

public function add($price)
{
$this->total = $this->total + $price;
}

public function getTotal()
{
return sprintf('\\%s-', number_format($this->total));
}
}

サブシステムの最後は、選択したパーツモデルを管理するクラスです。

App/Bto/Choose.php
<?php

namespace App\Bto;


class Choose
{
private $chooses = [];

public function getChooses()
{
return $this->chooses;
}
public function add($item)
{
$this->chooses[] = $item;
}
}

さて。これで選択情報・パーツ情報・金額処理を扱うクラスが揃ったのでクライアントから利用し一連のBTOシステムを構築してみます。

と言いたいところですが、これらをクライアントで利用しようとすると結構な数の処理の取り回しが必要になる事が想像できます。これをクライアント側に投げるのは少し酷なようです。

ここでFacadeパターンの出番です。クライアントコードに着手する前に、Facadeクラスを作成します。

App/Bto/BtoFacade.php
<?php

namespace App\Facade;

use App\Bto\MachineModel;
use App\Bto\PartsCpu;
use App\Bto\PartsHdd;
use App\Bto\PartsMemory;
use App\Bto\TotalFee;
use App\Bto\Choose;

class BtoFacade
{
private $machine_model;
private $parts_cpu;
private $parts_memory;
private $parts_hdd;
private $total_fee;
private $choose;

public function __construct()
{

$this->machine_model = new MachineModel();
$this->parts_cpu = new PartsCpu();
$this->parts_memory = new PartsMemory();
$this->parts_hdd = new PartsHdd();
$this->total_fee = new TotalFee();
$this->choose = new Choose();
}


public function chooseModel($id)
{
$item = $this->machine_model->getItem($id);
$this->choose->add($item);
$this->total_fee->add($item->getPrice());
}
public function chooseCpu($id)
{
$item = $this->parts_cpu->getItem($id);
$this->choose->add($item);
$this->total_fee->add($item->getPrice());
}
public function chooseMemory($id)
{
$item = $this->parts_memory->getItem($id);
$this->choose->add($item);
$this->total_fee->add($item->getPrice());
}
public function chooseHdd($id)
{
$item = $this->parts_hdd->getItem($id);
$this->choose->add($item);
$this->total_fee->add($item->getPrice());
}

public function Total()
{
foreach ($this->choose->getChooses() as $item) {
$item->display();
}
echo sprintf('<br>合計 %s', $this->total_fee->getTotal());
}
}

Facadeクラスにて、関連クラスを用いた情報取得・保持・参照・合計処理までを実装しています。

それぞれのメソッド上、つまりは1つの処理に対して複数のクラスが絡む事は良くありますが、Facadeパターンではこの辺りも吸収してこのようにすっきりと処理をまとめる事ができます。そしてクライアント側では、関連するクラスを意識せずにFacadeクラスのみを用いて一連の処理を実現できるようになります。

では、クライアントからこれらを使用します。

index.php
<?php

use App\Facade\BtoFacade;

$bto = new BtoFacade();
$bto->chooseModel(rand(1, 3));
$bto->chooseCpu(rand(1, 3));
$bto->chooseMemory(rand(1, 3));
$bto->chooseHdd(rand(1, 3));

echo $bto->Total();

各choose()メソッドでは、選択したパーツのIDを渡してパーツ情報をセットしています。(今回は、ランダムに番号を発行しています)

そして、Total()メソッドで結果を出力している。これだけです。

結果は以下のようになります。

Facadeパターン実行結果

Facadeパターンを用いてクライアントから一連の処理をシンプルに使う事が出来ました。

まとめ

Facade = 正面・窓口」を意味する通り、Facadeクラス自体は特別な処理を持たず、サブシステムを束ね処理を中継する事に注力します。そしてこれによってクライアント側では、複雑なクラス同士の絡みや処理の手順などを気にする事なく、シンプルに処理を利用できるようになります。

今回の実装例ではサブシステムをまとめる為に単純なクラスを複数作成しましたが、実際の現場ではその1つ1つの機能や役割が大きな責務を負っているクラスも多いので、クライアント側から無作為に利用するよりも、一連の処理の流れのまとまりとしてFacadeパターンを用いると、システム構築もシンプルになります。是非試してみてください。