クラスデザインパターンのFactory Methodパターンがよくわからなかったのでまとめてみた
問題提起
ある共通クラスAppを作ろうとしている
このAppクラスは、ConcreteProductAクラスを動作させ、終了させるという処理を行う
public class App {
// 業務ロジック
public void anOperation(){
ConcreteProductA product = new ConcreteProductA();
product.DoWork();
product.Close();
}
}
ConcreteProductBクラスにも対応するとなった時は、
条件分岐が増えることになる
public class App {
public void anOperation(int type){
switch(type){
case 1:
ConcreteProductA product = new ConcreteProductA();
product.DoWork();
product.Close();
break;
case 2:
ConcreteProductB product = new ConcreteProductB();
product.DoWork();
product.Close();
break;
//... 以降どんどん増える
}
}
}
このやり方には問題がある
- 対応する製品が増えるたびに業務ロジックを修正する手間がかかる
- 対応する製品と強く結合しているため、汎用性(再利用性)を失って生産性が低い
- メソッド、分岐の巨大化による可読性、保守性の低下
Factory Method
まず、製品クラスについてインターフェースとして抽象化する
public interface IProduct {
void DoWork();
void Close();
}
public class ConcreteProductA : IProduct {
void DoWork(){ ... }
void Close(){ ... }
}
public class ConcreteProductB : IProduct {
void DoWork(){ ... }
void Close(){ ... }
}
業務ロジックで、製品クラスをnewしない
インスタンス生成はサブクラスで実装するようにabstractとする
public abstract class App {
protected abstract IProduct FactoryMethod();
public void AnOperation(){
IProduct product = FactoryMethod();
product.DoWork();
product.Close();
}
}
Appクラスを継承した、
ConcreteProductAやConcreteProductBを生成するサブクラスを作成する
public class ProductAApp : App {
override protected IProduct FactoryMethod() => new ConcreteProductA();
}
public class ProductBApp : App {
override protected IProduct FactoryMethod() => new ConcreteProductB();
}
この共通ライブラリを使いたいユーザーが、
ProductAapp、ProductBappどちらを使うか選択する
// クライアントのコード
// どの Creator を使うか“だけ”を選ぶ
static void Main(string[] args)
App app;
switch(argc[0]){
case "A": app = new ProductAApp(); break;
case "B": app = new ProductBApp(); break;
}
app.AnOperation();
}
問題に対する効果
対応する製品が増えるたびに業務ロジックを修正する手間がかかる点
共通ライブラリ側は業務ロジック(AnOperation())の修正が不要
対応する製品のAppサブクラスを追加するだけでよい
対応する製品と強く結合しているため、汎用性(再利用性)を失って生産性が低い点
抽象化されているので汎用性は高い
メソッド、分岐の巨大化による可読性、保守性の低下
業務ロジックが修正不要なので可読性、保守性は維持される
採用場面
実体は複数あるが、処理フローは共通化できる時
条件分岐ではなくinterfaceで表現できるかで判断できると思われる
メモ
ここでいう App が GoF の Creator、AnOperation() が anOperation() に相当
合成バージョン
合成(Composition)とは、あるクラスが他のクラスのインスタンスを内部に持ち、
内部に持った他クラスの機能を利用して自身の機能を構成する設計手法
内部に持つクラスを切り替えることでポリモーフィズムを実現する
これを取り入れる場合、Appクラスのサブクラスを作成せず、
インスタンス生成部分だけを外部から渡してもらう形になる
class App {
private readonly IProductFactory _factory;
public App(IProductFactory factory) => _factory = factory;
public void AnOperation() {
var p = _factory.Create();
p.DoWork();
p.Close();
}
}
インスタンス生成部分だけを抽出 (IProductFactoryインターフェース)
実体の生成はAppのサブクラスではなく、
このインターフェースを継承したFactoryクラスに実装する
public interface IProductFactory {
IProduct Create();
}
public class ProductAFactory : IProductFactory {
public IProduct Create() => new ProductA();
}
public class ProductBFactory : IProductFactory {
public IProduct Create() => new ProductB();
}
呼び出し方
// クライアントのコード
// どの Factory を使うか“だけ”を選ぶ
public static void Main(string[] args){
IProductFactory factory;
switch(argc[0]){
case "A": factory = new ProductAFactory(); break;
case "B": factory = new ProductBFactory(); break;
}
App app = new App(factory);
app.AnOperation();
}
DIインジェクションにより動作を切り替えたりしたいときに有効
自動テストも書きやすくなる