今回実施していくのは、Factory Methodパターンです。
Factory?
Laravelでもよく目にするな
Laravelを使ったことがある人は、Factoryというクラスに馴染みがあると思います。
Factoryはインスタンスを生成する処理を持ちます。
こういったFactory系のパターンは3つあり
- Factoryパターン
- Factory Methodパターン
- Abstract Factoryパターン
とあります。
今回紹介するのはFactory Methodパターンです。他の二つは別の機会にやっていきたいと思います。
Factory Methodパターンとは
コンストラクタの代わりに、インスタンスの生成するメソッド(Method)を作るから「FactoryMethod」パターンです。
public static function FactoryMethod(): Product
{
return new Product();
}
なぜこれがいいのか?
これを理解するのが難しいです。
まず、お伝えしたいこと
まず、お伝えしなければいけないことは、よく目にするFactoryとは異なります。
Factory メソッドなので、インスタンスを生成するメソッドを持つパターンだと思ってください。
クラスというよりもメソッドです。ここが注意です。
そして最終的な目的は、DIP(依存性逆転の原則)です。
FactoryMethodパターンは、new ClassName()のインスタンスを生成しないための手法です。
ここを理解しておいてください。
でないと「????」と混乱します。
登場する役割
役割名 | 役割 |
Creator | インスタンスを生成するメソッド(Factory Method)を持つクラス |
Product | Factory Methodによって、インスタンスが生成されるクラス |
関係性
このようにインターフェースという抽象に依存させることで、別の具象クラスに切り替えても変更点は一箇所で完結します。
具体的なコード
上位モジュール
<?php
namespace Business // ビジネスロジック
{
class Logic
{
/**
* Productを生成する処理
*
* @var ICreator
*/
private ICreator $creator;
public function __construct(ICreator $creator)
{
$this->creator = $creator;
}
/**
* 実行したいロジック
* Productを生成して、情報を加工する
*
* @return IProduct
*/
public function Do(): IProduct
{
$product = $this->creator->FactoryMethod();
// 処理を実行する
return $product;
}
}
/**
* Productを生成するインターフェース
* FactoryMethodを保持する
*/
interface ICreator
{
/**
* Productのインスタンスを生成
*
* @return IProduct
*/
public function FactoryMethod(): IProduct;
}
/**
* 生成対象のインターフェース
*/
interface IProduct { }
}
下位モジュール
<?php
namespace Main
{
class Program
{
/**
* Undocumented function
*
* @param array $args
* @return void
*/
public static function Main(array $args): void
{
$logic = new Logic(new Creator());
$logic->do();
}
}
class Creator implements ICreator
{
/**
* Productのインスタンスを生成
*
* @return IProduct
*/
public function FactoryMethod(): IProduct
{
return new Product();
}
}
class Product implements IProduct { }
}
依存関係を追う
LogicクラスはICreator
というインターフェースをコンストラクタで取り込む。
つまり、抽象に依存が成立しています。
class Logic
{
/**
* Productを生成する処理
*
* @var ICreator
*/
private ICreator $creator; // 抽象で依存させる。
public function __construct(ICreator $creator) // 抽象で依存させる。
{
$this->creator = $creator; // DI 依存性の注入
}
では続いて、ICreator
を見ていきましょう。
この中もIProduct
というインターフェースを返り値にしています。
つまり、抽象に依存が成立しています。
/**
* Productを生成するインターフェース
* FactoryMethodを保持する
*/
interface ICreator
{
/**
* Productのインスタンスを生成
*
* @return IProduct // 抽象で依存
*/
public function FactoryMethod(): IProduct; // 対象のインスタンスを取得
}
ここでようやく取り出したいIProductにたどり着きます。
/**
* 生成対象のインターフェース
*/
interface IProduct { }
この一連の流れはすべて抽象に依存しています。
FactoryMethod()を追う
今回FactoryMethodパターンということもあり、メソッドを追いかけましょう。
class Logic
{
/**
* Productを生成する処理
*
* @var ICreator // 抽象に依存
*/
private ICreator $creator;
public function __construct(ICreator $creator)
{
$this->creator = $creator; // DI 依存性の注入。抽象に依存
}
/**
* 実行したいロジック
* Productを生成して、情報を加工する
*
* @return IProduct // 抽象に依存
*/
public function Do(): IProduct // 抽象に依存
{
$product = $this->creator->FactoryMethod(); // ICreatorのFactoryMethod()でインスタンスを生成
// 処理を実行する
return $product;
}
}
このLogicクラスに具象クラスの依存関係がないことがわかるでしょうか?
全部抽象の依存で完結させています。
これが達成できるので、インターフェースにインスタンスを生成するFactoryMethod()を定義するのです。
これがFactoryMethodパターンです。
まとめ
インターフェースという抽象で依存することで、具象クラスによる依存を回避できました。
これならまるっと具象クラスを変更しても土台には影響しません。
1箇所だけなら、あまり価値を感じづらいかもしれませんが、10箇所・100箇所と依存箇所が増えれば増えるほど、その価値は高まります。
なぜなら、依存させているのは抽象であるインターフェースなので、
インターフェースと具象クラスの結びつきをしている、たった一箇所を変更するだけで、すべて更新されます。
つまり、変更点が少なくて済みます。
その実現のために、FactoryMethodパターンでインタンスを生成したのです。
やりたかったのは抽象で依存させるという(DIP:依存性逆転の原則)を実現するために、インターフェースでインタンスを生成することでした。Factory Method の利点である「具象クラスを書かずにインスタンスを生成する」という文章を「依存」で置き換えると、「具象クラスに依存せずにインスタンスを生成する」ということになります。
コメント