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なので型が記載されていないですが implclass 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

--

--