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
パターンと呼ばれる