pythonによるデザインパターン[Bridge]
# Intro
この文章は結城 浩さんの「Java言語で学ぶデザインパターン入門」を、pythonで実装してみたサンプルコードです。
筆者の環境は以下の通りです。Python 3.6.3
まだ修行中の身なので間違いがあると思いますがご了承ください。 今回は「Bridge」です
# Explanation
本書のp122によると、 Bredgeとは
「機能のクラス階層」と「実装のクラス階層」を橋渡しする
とあります。まず「機能のクラス階層」と「実装のクラス階層」について説明していきます。
##「機能のクラス階層とは」
機能のクラス階層とは単なる継承のことです。後ほどの具体例で出てくるのですが、例えば文字を1行表示する class Display
というものがあるとします。次に5行文字を表示する機能をつけるとき、 class Display
を継承してclass CountDisplay
を作るとします。このように機能をつけるために、継承することでできる階層を「機能のクラス階層」と呼びます。
##「実装のクラス階層とは」
実装のクラス階層とは、抽象クラスによる継承のことです。抽象クラスでは抽象メソッドでインターフェースを定義します。具体的な中身を実装するために抽象クラスを継承し、サブクラスで関数を実装します。この抽象クラスによる継承でできる階層を「実装のクラス階層」と呼びます。
以上の2つの階層が理解できたところで何が問題となるのでしょうか?
問題となるのは、実装のクラス階層に機能のクラスを混ぜたいときです。つまり、抽象クラスに実装クラスとは別に、機能を付け加えるクラスを継承したいときです。この時抽象クラスを継承して、機能(新たな関数)だけを付け加えるだけだと、エラーが出ます。
Can't instantiate abstract class ... with abstract methods
抽象クラスを継承するなら、抽象メソッドを全て実装していないといけないので、実装クラスと全く同じような関数を記述しなくてはいけません。これは重複したプログラムとなりDRYに反しますね。
そこで登場するのがBridgeです。
# Practice
## Specification
ある文字列(Hello, Japan.
とする)を与えると、以下のように囲って表示するメソッドを作成してください。
+-------------+
|Hello, Japan.|
+-------------+
ここで、表示するときは
- 上の模様(
+ — — — — — — +
)を表示する関数 —def open
- 真ん中の文字(
|(string)|
)を表示する関数 —def pprint
- 下の模様(
+ — — — — — — +
)を表示する関数 —def close
に分けて定義し、抽象クラス( class DisplayImpl
)、サブクラス(class StringDisplayImpl)
に分けて実装してください。
また、その表示方法に、以下のように文字を5回登場させる機能( def multi_display
)をもつクラス(class CountClass
)を別に作ってください。
+----------------+
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
+----------------+
## Sample Code
## Explanation
新しく class Display
を作成し、抽象クラス( class DisplayImpl
)と機能を付け加えるクラス( class CountDisplay
)に挟み込みます。ざっくりとしたクラス図は以下の通りです。
<class Display> ------> <class DisalayImpl> ^ ^
| |
| |
| |<class CountDisplay> <class StringDisplayImpl>
重要なのは class Display
です。
class Display:
def __init__(self, impl):
self.impl = impl def open(self):
self.impl.raw_open() def pprint(self):
self.impl.raw_pprint() def close(self):
self.impl.raw_close() def display(self):
self.open()
self.pprint()
self.close()
まず impl
というFieldを定義します。pythonなので型が記載されていないですが impl
は class DisplayImpl
です。つまり、自分自身の変数に抽象クラスのインスタンスを入れこみます。
そして各関数の実装は impl
のつまりclass DisplayImpl
の関数を実行していることになります。
これによって抽象クラス( class DisplayImpl
)と機能をつけるクラス( class CountDisplay
)の橋渡し(Bridge)となり、class CountDisplay
は継承によって機能( def multi_display
)を付け加えることができます。
class CountDisplay(Display):
def __init__(self, impl):
super().__init__(impl) def multi_display(self, times):
self.open()
for _ in range(times):
self.pprint()
self.close()
# Conclusion
Bridgeパターンによって、実装のクラスと機能のクラスを明瞭に分けることができて、クラス設計がし易くなりますね。
クラス間を結びつけるときに継承を用いると、とても固い結びつきで、頻繁にクラスの関係を大きく変えたいときに大変です。一方Bridgeのように、クラスにField(impl
)を置いてそのFieldに別のインスタンスを代入して、クラス間を結びつけることを委譲(delegation)と呼びます( — p131 あまりちゃんと理解してないので間違っているかもしれません)。
実装のクラス機能のクラスを分けたり、継承・委譲を使い分けて、綺麗なクラス設計をしていきたいですね。
# ref
- 「増補改訂版 Java言語で学ぶデザインパターン入門」 結城浩(著)第11版