Bridge
1. 目的
- 機能と実装を切り離したい
- 同じインターフェイスを持つ複数の実装があるが、それらを任意に切り替えて利用したい
2. 課題
- 機能を提供するクラスは1つにしたまま、実装を切り替えられるようにしておきたい
3. 解決策
- 機能を表すクラスと実装を表すクラスをそれぞれ別個に切り出す
- 切り出した実装クラスを機能クラスに与え具体的な処理を実装クラスに委譲する形にする
- 以下のサンプルでは機能を表現するクラスを
Feature
、実装を表すクラスをImplement
とした
class Feature:
def __init__(self, impl):
self.impl = impl
def exec(self):
self.impl.exec()
class Implement():
def exec(self):
print('Year!')
if __name__ == '__main__':
f = Feature(Implement())
f.exec() # Year!
- 上記のようにすることで、機能クラスは実装クラスを任意に切り替えて実行することができる
- 尚、上記のサンプルではコンセプトを示すため諸々省略している(本来は
Implement
はInterfaceとし、それらを実装したクラスを機能クラスに与えることになる)
- PythonのようにDuck Typingを採用している言語の場合は同じ名前のメソッドがあればよいのでサブクラスの関係を厳密に気にする必要は(あまり)ない
4. メリット
- 機能クラスを変更することなく、実装クラスを切り替えて使用することができる
- 実装クラスをモック化したユニットテストが作成しやすくなる(実装クラスをコンストラクタインジェクションしている為)
5. デメリット
構造が複雑になる
- 構成要素が増える為、プログラムの構造は複雑化する
- 実装を切り替えて利用したいという明確な要求が無いのであれば適用するべきではない
-
YAGNI原則を参照
6. 注意事項
(1) Factoryパターンとの併用必須
- 別クラスとして切り出した実装を機能クラスに適用する為には適切な実装クラスを選択する必要があるが、その為の判定がコードのあちこちに散らばると保守性を低下させてしまう
- 適切な実装クラスを返却する判定処理はFactoryに隠蔽し、利用側はFactory経由で実装クラスを取得するようにするべきである
- ↓はPythonでのサンプル
class Feature:
def __init__(self, impl):
self.impl = impl
def exec(self):
self.impl.exec()
class Factory:
@classmethod
def create(cls, impl_type):
if impl_type == '01': return Implement01()
if impl_type == '02': return Implement02()
raise ValueError('invalid argument')
class Implement(metaclass=ABCMeta):
@abstractmethod
def exec(self):
pass
class Implement01(Implement):
def exec(self):
...
class Implement02(Implement):
def exec(self):
...
if __name__ == '__main__':
impl1 = Factory.create('01')
f1 = Feature(impl1)
f1.exec()
impl2 = Factory.create('02')
f2 = Feature(impl2)
f2.exec()
(2) クロージャならクラスを定義する必要なし
- クロージャ(関数オブジェクト)を扱える言語(JavaScript、Pythonなど)の場合、クラスを定義せずクロージャを渡せば同じ目的が達成できる
- 単純にコードが短くシンプルになるのでクロージャが使える言語ではこちらの方式を使った方がよい
- Pythonだと↓のイメージ
class Feature:
def __init__(self, fn):
self.fn = fn
def exec(self):
self.fn()
def function1():
print("実装1")
def function2():
print("実装2")
if __name__ == '__main__':
f1 = Feature(function1)
f1.exec()
f2 = Feature(function2)
f2.exec()
参考資料
Appendix-1
- Bridgeパターンの実例としてはPHPのu*sort関数群が挙げられる
- これらの関数群はソート処理の実装をコールバック関数として関数に与える形で処理を切り替えられるようにしており、クロージャを利用したBridgeパターンの実装例と言える