Simple Factory
1. 背景
オブジェクトを間接的に生成したい
- 基底クラスを継承したサブクラスやインターフェイスを実装したクラス群があり、それらを利用したいが、それら個別のクラスに対して直接依存することなくオブジェクトを生成したい
- 実装クラスとの依存関係は極力減らし、インターフェイスに対してのみ依存したい
2. 課題
オブジェクトを直接生成すると具体的なクラスとの結合が強くなってしまう
from abc import ABCMeta, abstractmethod
class Program(metaclass=ABCMeta):
@abstractmethod
def exec(self):
pass
class Program01(Program):
def exec(self):
print('わーい!')
class Program02(Program):
def exec(self):
print('うぇーい!')
class Program03(Program):
def exec(self):
print('うわー!')
class Client:
def run(self, type):
if type == '01':
p1 = Program01() # Program01と依存関係が発生
print(p1.exec())
if type == '02':
p2 = Program02() # Program02と依存関係が発生
print(p2.exec())
if type == '03':
p3 = Program03() # Program03と依存関係が発生
print(p3.exec())
if __name__=='__main__':
c = Client()
c.run('01') # => わーい!
c.run('02') # => うぇーい!
c.run('03') # => うわー!
- 親クラスを継承したサブクラスやインターフェイスを実装したクラス群を直接生成すると、それらの実装クラスとの依存関係が生じてしまう
- 依存関係が強いクラスは保守性が低下する要因になるのでこれは避けたい
- 上記の
ClientクラスではClientクラスの中で直接Programのサブクラス群を生成している為、それらと依存関係が生じている
- また、
typeで生成するクラスを判断する条件分岐があるが、この条件分岐がClientクラス以外にも書かれるようになるとDRY原則に反することになる(同じ判定処理が複数箇所に散在することになる)
3. 解決策
オブジェクトの生成処理を専門に行うクラスを追加する
- オブジェクトの生成を専門に行う
Factoryクラスを追加し、ProgramクラスとそのサブクラスはFactoryクラスを経由して生成する
- そのようにする事で
ClientクラスはProgramクラスのサブクラスと個別に依存関係を作る必要がなくなり、直接依存関係にあるクラスはFactoryクラスだけになる
ClientクラスはProgramクラスのインターフェイス(ここではexecメソッド)を呼び出すことだけを知っておけばよい
from abc import ABCMeta, abstractmethod
class Program(metaclass=ABCMeta):
""" Programのインターフェイス """
@abstractmethod
def exec(self):
pass
class Program01(Program):
""" Programの実装1 """
def exec(self):
print('わーい!')
class Program02(Program):
""" Programの実装2 """
def exec(self):
print('うぇーい!')
class Program03(Program):
""" Programの実装3 """
def exec(self):
print('うわー!')
class Factory:
""" ProgramのFactoryクラス """
@staticmethod
def create(type):
if type == '01': return Program01()
if type == '02': return Program02()
if type == '03': return Program03()
raise ValueError()
class Client:
def run(self, type):
p = Factory.create(type) # 依存関係にあるのはFactoryだけ & IF文も消えた
print(p.exec())
if __name__=='__main__':
c = Client()
c.run('01') # => わーい!
c.run('02') # => うぇーい!
c.run('03') # => うわー!
4. メリット
利用側のクラスは実装クラスのインターフェイスのみ知っていればよくなる
- 利用側の処理(ここでは
Clientクラス)が具体的な実装(Programのサブクラス群)と直接的な依存関係にあると、個別の実装や前提条件に影響を受けやすくなる
Factoryクラスを経由してProgramのサブクラス群を作成することで、利用側のクラスはProgramの具体的な実装(サブクラス群)を知っておく必要がなくなる
実装が増えても呼び出し元に影響しない
- 利用側のクラスは
ProgramのサブクラスがProgramのインターフェイスに従ってさえいればサブクラスの種類がどれだけ増えても修正する必要なく実行することができる
- 実装クラスが増えてもFactoryクラスを修正するだけで利用しているクラスに対して変更する必要がなく、変更点を一箇所に限定できる
ユニットテストも容易になる
Factoryは単純なクラスなので、これをMock化すれば返却するProgramの実装クラスも任意に差し替えることができる
- ユニットテスト時は
FactoryをMock化し、Programの実装クラスもテスト用のダミー実装に変えることで効率よくテストを行うことができたりする
5. デメリット
構成要素が増える(=複雑度が上昇する)
Factoryクラスが追加されることでプログラムの構成要素が増加する為、プログラム全体の複雑度が上昇する
- ただし、一般的には
Factoryクラス追加に伴う複雑度の上昇よりもクラス間の依存関係を粗結合に保てることのメリットの方がが大きいことが多い
6. 注意事項
if文の集約/隠蔽に積極的に使用する
Simple Factoryパターンは構造が簡素でかつ分かりやすいコードになるので積極的に使用するべきである
- 何らかの条件を元にクラスを返却している処理は原則として
Simple Factoryにまとめてしまう方が保守性が向上する
- クラスの生成に関する条件分岐を一箇所にまとめることができるので、積極的に使用することが望ましい
関数として実装してもよい
- 典型的な
Simple Factoryはひとつのメソッドしか持たず、そのメソッドもスタティックメソッドとして実装されることが多い
- そのような場合はわざわざクラスを定義せず関数として実装した方がよい
# Programクラスの定義は省略
def factory(type):
""" ProgramのFactory関数 """
if type == '01': return Program01()
if type == '02': return Program02()
if type == '03': return Program03()
raise ValueError()
if __name__=='__main__':
p1 = factory('01')
c1 = Client(p1)
p2 = factory('02')
c2 = Client(p2)
p3 = factory('03')
c3 = Client(p3)
c1.run() # => わーい!
c2.run() # => うぇーい!
c3.run() # => うわー!
- GoFデザインパターンはJavaやC++を対象に考えられたものであり、当時のJavaは必ずクラスを定義する必要があったこと、UMLのクラス図で表現する為、あえてクラスを使っている側面がある
- 関数オブジェクトを使えばより簡潔に記述できるパターンは多い
参考資料
Appendix-1 Strategyパターンと併用したSimple Factoryパターン
- 本文のサンプルコードでは修正前コードとの対称性を重視して
Clientのメソッド内でFactoryを呼び出しているが、実はあまりよいコードとは言えない
ClientとFactoryが強く依存関係を持ってしまうのでダミー実装に差し替えることが難しいことが問題である
- ↓のコードのように
ClientクラスのコンストラクタにProgramの実装クラスを渡した方がよい(ClientとFactoryの依存関係も解消される)
from abc import ABCMeta, abstractmethod
class Program(metaclass=ABCMeta):
""" Programのインターフェイス """
@abstractmethod
def exec(self):
pass
class Program01(Program):
""" Programの実装1 """
def exec(self):
print('わーい!')
class Program02(Program):
""" Programの実装2 """
def exec(self):
print('うぇーい!')
class Program03(Program):
""" Programの実装3 """
def exec(self):
print('うわー!')
class Factory:
""" ProgramのFactoryクラス """
@staticmethod
def create(type):
if type == '01': return Program01()
if type == '02': return Program02()
if type == '03': return Program03()
raise ValueError()
class Client:
def __init__(self, program):
self.program = program
def run(self):
print(self.program.exec())
if __name__=='__main__':
p1 = Factory.create('01')
c1 = Client(p1)
p2 = Factory.create('02')
c2 = Client(p2)
p3 = Factory.create('03')
c3 = Client(p3)
c1.run() # => わーい!
c2.run() # => うぇーい!
c3.run() # => うわー!
- このコードのように
Clientクラスの初期化時に実装クラスのインスタンスを渡す方法はStrategyパターンと呼ばれる