Bridgeパターン

結城浩さんの「Java言語で学ぶデザインパターン入門」を題材にpythonデザインパターンを書いてみる。
今回の題材はBridgeパターン。このパターンは機能のクラス階層と実装のクラス階層を結びつけるというものらしい。
まずは機能のクラス階層と実装のクラス階層とは何なのかってことから勉強。
あるクラスclsがあって、clsに新しい機能を追加する(新しいメソッドの追加)ときにはclsのサブクラスとしてcls2をつくる。
これで一段階のクラス階層ができる。さらにcls2に新しい機能を追加するならcls2のサブクラスcls3をつくる。これで二段階の
クラス階層ができて、この階層は機能の追加のためにつくられた階層となる。これを機能のクラス階層とすると。
これに対して、あるクラスclsではメソッドの宣言だけを行い、そのメソッドの実装をclsのサブクラスcls2で行った場合、clsとcls2に
よってできるクラス階層を実装のクラス階層とする。
クラス階層が1つだと機能のクラス階層と実装のクラス階層が混ざってしまい、クラス階層が複雑になりプログラムの見通しが悪く
なる危険がある。そこで、機能のクラス階層と実装のクラス階層を2つの独立したクラス階層に分けることが大事みたい。しかし、分けただけではダメで2つのクラス階層をつなぐことが必要。Bridgeパターンはそのつなぐ役割を果たす。
サンプルプログラム(bridge.py) は何かを表示するというもの。具体的には以下のよう。
まずDisplayクラス

import random
import sys

class Display(object):
    def __init__(self, impl):
        self.impl = impl
    def opening(self):
        self.impl.rawOpening()
    def printing(self):
        self.impl.rawPrinting()
    def closing(self):
        self.impl.rawClosing()
    def display(self):
        self.opening()
        self.printing()
        self.closing()

このクラスは機能のクラス階層の最上位のクラス。コンストラクタのimplは実装を表すクラスのインスタンスが渡される。
このインスタンスが2つのクラス階層をつなぐ。
次はCountDisplayクラス

class CountDisplay(Display):
    def __init__(self, impl):
        super(CountDisplay, self).__init__(impl)
    def multiDisplay(self, times):
        self.opening()
        for i in range(times):
            self.printing()
        self.closing()

このクラスはDisplayクラスに指定回数表示するという機能(multiDisplayメソッド)を追加したもの。
よってこれは機能のクラス階層。
次はDisplayImplクラス

class DisplayImpl(object):
    def rawOpening(self):
        pass
    def rawPrinting(self):
        pass
    def rawClosing(self):
        pass

このクラスは実装のクラス階層の最上位のクラス。Displayクラスのopening,printing,closingメソッドに対応するメソッドの
宣言のみ。
次はStringDisplayImplクラス

class StringDisplayImpl(DisplayImpl):
    def __init__(self, st):
        self.st = st
        self.width = len(st)
    def rawOpening(self):
        self.printLine()
    def rawPrinting(self):
        print "|%s|" %self.st
    def rawClosing(self):
        self.printLine()
    def printLine(self):
        print "+%s+" %("-"*self.width)

このクラスはDisplayImplクラスのサブクラスとしてメソッドを実装。ここは実装のクラス階層。
ここにさらにクラスを追加して、ランダム回数表示する処理を実現するにはどうするか?
機能のクラス階層に追加すれば良いのではってことでRandomDisplayクラス

class RandomDisplay(CountDisplay):
    def random_Display(self, times):
        self.multiDisplay(random.randrange(times))

さらにクラスを追加して、テキストファイルの内容を表示する処理を実現するにはどうするか?
実装のクラス階層に追加すれば良いのではってことでTextDisplayImplクラス

class TextDisplayImpl(DisplayImpl):
    def __init__(self, textfile):
        self.textfile = textfile
    def rawOpening(self):
        self.f = open(self.textfile)
    def rawPrinting(self):
        print self.f.readlines()[0]
    def rawClosing(self):
        self.f.close()

さらにクラスを追加して、

<>
<*>
<**>
<***>

とか、

|-
|##-
|####-
|######-
|########-
|##########-

みたいな模様を表示するにはどうするか?
今度は機能と実装の両階層に追加すれば良いのではってことでIncreasingDisplayクラスとDecoDisplayImplクラス

class IncreasingDisplay(CountDisplay):
    def __init__(self, impl, increase):
        super(IncreasingDisplay, self).__init__(impl)
	self.increase = increase
    def increasing_Display(self, max_number):
        self.max_number = max_number
	for i in range(0, max_number, self.increase):
	    self.multiDisplay(i)

class DecoDisplayImpl(DisplayImpl):
    def __init__(self, start, deco, last):
        self.start = start
        self.deco = deco
        self.last = last
    def rawOpening(self):
        sys.stdout.write(self.start)
    def rawPrinting(self):
        sys.stdout.write(self.deco)
    def rawClosing(self):
        sys.stdout.write("%s\n" %(self.last))

最後に動作テスト部分

if __name__=="__main__":
    d1 = Display(StringDisplayImpl("Hello, Japan."))
    d2 = CountDisplay(StringDisplayImpl("Hello, World."))
    d3 = CountDisplay(StringDisplayImpl("Hello, Universe."))
    d4 = RandomDisplay(StringDisplayImpl("Hello, Random."))
    d5 = Display(TextDisplayImpl("newfile.txt"))
    d6 = IncreasingDisplay(DecoDisplayImpl("<", "*", ">"), 1)
    d7 = IncreasingDisplay(DecoDisplayImpl("|", "#", "-"), 2)
    d1.display()
    d2.display()
    d3.display()
    d3.multiDisplay(5)
    d4.random_Display(5)
    d5.display()
    d6.increasing_Display(4)
    d7.increasing_Display(12)

実行結果は以下。

C:\works\python\book1>python bridge
+-------------+
|Hello, Japan.|
+-------------+
+-------------+
|Hello, World.|
+-------------+
+----------------+
|Hello, Universe.|
+----------------+
+----------------+
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
+----------------+
+--------------+
|Hello, Random.|
|Hello, Random.|
|Hello, Random.|
+--------------+
test! test! test!
<>
<*>
<**>
<***>
|-
|##-
|####-
|######-
|########-
|##########-