GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。ただ、取り上げられている実例は、JAVAベースのため、自分の理解を深めるためにも、Pythonで同等のプラクティスに挑んでみました。
■ Bridge(ブリッジ・パターン)
Bridgeパターン(ブリッジ・パターン)とは、GoF(Gang of Four; 4人のギャングたち)によって定義されたデザインパターンの1つである。 「橋渡し」のクラスを用意することによって、クラスを複数の方向に拡張させることを目的とする。
UML class and sequence diagram
UML class diagram
□ 備忘録
Bridgeパターンは、機能のクラス階層と実装のクラス階層を橋渡しするらしいです。
(1) 機能のクラス階層とは?
あるクラスに対して、新しい機能を追加したい場合、新しくサブクラスを定義した上で、メソッドを実装します。
既存のスーパークラスと、新たに定義したサブクラスの関係が、"機能のクラス階層"になります。
一般的には、以下のような関係を想定します。
- スーパークラスは基本的な機能を持っている
- サブクラスで新しい機能を追加する
(2) 実装のクラス階層とは?
新しい実装を追加したい場合、抽象クラスから派生した具体的なサブクラスを定義した上で、メソッドを実装します。
既存の抽象クラスと、新たに派生した具体的なサブクラスの関係が、"実装のクラス階層"になります。
一般的には、以下のような関係を想定します。
- 抽象クラスでは、抽象メソッドによってインタフェースを規定している
- 派生したサブクラスは具象メソッドによってそのインタフェースを実装する
■ "Bridge"のサンプルプログラム
実際に、Bridgeパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。
機能のクラス階層と実装のクラス階層を橋渡しを想定したサンプルになります。
-
DisplayFunc
と、DisplayStringImpl
の橋渡しを経て、文字列を表示する -
DisplayCountFunc
と、DisplayStringImpl
の橋渡しを経て、文字列を表示する -
DisplayCountFunc
と、DisplayStringImpl
の橋渡しを経て、文字列を表示する -
DisplayRandomFunc
と、DisplayStringImpl
の橋渡しを経て、文字列を5回繰り返して表示する -
DisplayRandomFunc
と、DisplayStringImpl
の橋渡しを経て、文字列をランダム回数繰り返して表示する -
DisplayFunc
と、DisplayTextfileImpl
の橋渡しを経て、テキストファイルの内容を表示する
$ python Main.py
+-----------+
|Hello Japan|
+-----------+
+-----------+
|Hello Japan|
+-----------+
+--------------+
|Hello Universe|
+--------------+
+--------------+
|Hello Universe|
|Hello Universe|
|Hello Universe|
|Hello Universe|
|Hello Universe|
+--------------+
+--------------+
|Hello Universe|
|Hello Universe|
|Hello Universe|
|Hello Universe|
+--------------+
aaa
bbb
ccc
ddd
eee
fff
ggg
サンプルプログラムを動かしただけだと、いまいち、何がしたいのかよく分かりませんね。
つづいて、サンプルプログラムの詳細を確認していきます。
■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern/tree/master/Bridge
- ディレクトリ構成
.
├── Main.py
├── bridge
│ ├── __init__.py
│ ├── function
│ │ ├── __init__.py
│ │ ├── display_count_func.py
│ │ ├── display_func.py
│ │ └── display_random_func.py
│ └── implement
│ ├── __init__.py
│ ├── display_impl.py
│ ├── display_string_impl.py
│ └── display_textfile_impl.py
└── test.txt
(1) Abstraction(抽象化)の役
Implement
役のメソッドを使って、基本的な機能だけが実装されているクラスです。
サンプルプログラムでは、DisplayFunc
クラスが、この役を努めます。
class DisplayFunc(object):
def __init__(self, impl):
self.impl = impl
def open(self):
self.impl.rawOpen()
def print_body(self):
self.impl.rawPrint()
def close(self):
self.impl.rawClose()
def display(self):
self.open()
self.print_body()
self.close()
(2) RefinedAbstraction(改善した抽象化)の役
Abstraction
役に対して機能を追加した役です。
サンプルプログラムでは、DisplayCountFunc
クラスと、DisplayRandomFunc
クラスが、この役を努めます。
from bridge.function.display_func import DisplayFunc
class DisplayCountFunc(DisplayFunc):
def __init__(self, impl):
super(DisplayCountFunc, self).__init__(impl)
def multiDisplay(self, times):
self.open()
for _ in range(times):
self.print_body()
self.close()
import random
from bridge.function.display_func import DisplayFunc
class DisplayRandomFunc(DisplayFunc):
def __init__(self, impl):
super(DisplayRandomFunc, self).__init__(impl)
def randomDisplay(self, times):
self.open()
t = random.randint(0, times)
for _ in range(t):
self.print_body()
self.close()
(3) Implementor(実装者)の役
Abstraction
役のインタフェースを実装するためのメソッドを規定する役です。
サンプルプログラムでは、DisplayImpl
クラスが、この役を努めます。
from abc import ABCMeta, abstractmethod
class DisplayImpl(metaclass=ABCMeta):
@abstractmethod
def rawOpen(self):
pass
@abstractmethod
def rawPrint(self):
pass
@abstractmethod
def rawClose(self):
pass
(4) ConcreteImplementor(具体的な実装者)の役
具体的にImplement
役のインタフェースを実装する役です。
サンプルプログラムでは、DisplayStringImpl
クラスと、DisplayTextfileImpl
クラスが、この役を努めます。
from bridge.implement.display_impl import DisplayImpl
class DisplayStringImpl(DisplayImpl):
def __init__(self, string):
self.string = string
self.width = len(string)
def rawOpen(self):
self.printLine()
def rawPrint(self):
print("|{0}|".format(self.string))
def rawClose(self):
self.printLine()
print("")
def printLine(self):
line = '-' * self.width
print("+{0}+".format(line))
from bridge.implement.display_impl import DisplayImpl
class DisplayTextfileImpl(DisplayImpl):
def __init__(self, filename):
self.filename = filename
def rawOpen(self):
filename = self.filename
self.f = open(filename, "r")
def rawPrint(self):
data = self.f.read()
data = data.split('\n')
for l in data:
print(l)
def rawClose(self):
self.f.close()
(5) Client(依頼人)の役
サンプルプログラムでは、startMain
メソッドが、この役を努めます。
from bridge.function.display_func import DisplayFunc
from bridge.function.display_count_func import DisplayCountFunc
from bridge.function.display_random_func import DisplayRandomFunc
from bridge.implement.display_string_impl import DisplayStringImpl
from bridge.implement.display_textfile_impl import DisplayTextfileImpl
def startMain():
d1 = DisplayFunc(DisplayStringImpl("Hello Japan"))
d2 = DisplayCountFunc(DisplayStringImpl("Hello Japan"))
d3 = DisplayCountFunc(DisplayStringImpl("Hello Universe"))
d4 = DisplayRandomFunc(DisplayStringImpl("Hello Universe"))
d5 = DisplayFunc(DisplayTextfileImpl("test.txt"))
d1.display()
d2.display()
d3.display()
d3.multiDisplay(5)
d4.randomDisplay(5)
d5.display()
if __name__ == '__main__':
startMain()