Update 2023.11.18 2017.05.05

Python デザインパターン サンプルコード Bridge
結城 浩「Java言語で学ぶデザインパターン入門」をPython化
Python3(3.11)で動くソースコード(.pyファイル .ipynbファイル)あります
「anaconda3」on .py「PyCharm」.ipynb「Jupyter Notebook」

◆◆Bridge パターンの使われる場面◆◆

GoFの言っていることは判りにくいが,「抽出されたクラス」とは機能のことで,「実装」とはサポート環境やバージョンなどのことです。(GoF本の和訳はいまいちです)

サポート環境やバージョンなどとは,
  Windows向け,Linux向け,MacOS向け
  プリンタ用,ディスプレイ用,通信用
  巨人向け,阪神向け,楽天向け
などいろいろ考えられます。

これら向けに機能もいろいろ追加したい場面では,機能ごとにすべての環境向けやバージョン向けに実装するのはたいへんな手間になってしまいます。

そこでGoFがいうように,機能側と環境側を独立に変更,拡張,再利用することができるように機能側と環境側の橋渡し(Bridgeという)を工夫するのがBridge パターンです。


(2023-11-18)Python3.11で動作確認済み


◆◆Bridge パターンとは◆◆

GoFによれば,Bridge パターンの目的は, 「抽出されたクラスと実装を分離して,それらを独立に変更できるようにする。」

GoFによれば,Bridge パターンの別名は,Handle/Body パターンだそうだ。

GoFによれば,次のような場合に,Bridge パターンを利用する。
・抽出されたクラスとその実装を永続的に結合することを避けたい場合。たとえば,実装を実行時に選択したり交換したりしなければならないときに,このような場合が起こり得る。
・抽出されたクラスとその実装の両方を,サブクラスの追加により拡張可能にすべき場合。この場合,Bridge パターンを用いることで,抽出されたクラスに異なる実装を結合したり,それぞれを独立に拡張することが可能になる。
・抽出されたクラスの実装における変更が,クライアントに影響を与えるべきではない場合。すなわち,クライアントのコードを再コンパイルしなくても済むようにすべき場合。
(以下省略)

GoFによれば,Bridge パターンの関連するパターンは次のようなものである。
Abstract Factory パターン: このパターンにより,Bridge パターンに基づくインスタンスの生成と構築を行うことができる。
Adapter パターン: このパターンは,関係のないクラス同士をつなぐことが目的である。このパターンは通常は設計が終わった後で適用される。それに対して,Bridge パターンは,抽出されたクラスと実装を独立に変更可能にするために設計の前段階で使われる。

◆◆Bridge パターンのサンプルのクラス構成◆◆

ソースコードは巻末にあり,ソースファイルはこのWebの左上隅にありダウンロードできます。

          Display                            DisplayImpl
    (open, print, close, display)    (rawOpen, rawPrint, rawClose)
             ↑                                  ↑
        CountDisplay                       StringDisplayImpl
        (multiDisplay)             (rawOpen, rawPrint, rawClose, printLine)
class Display は,機能側の上位クラスです。
class CountDisplay は,機能側の追加されたメソッドを持つクラスです。
class DisplayImpl は,環境側の上位クラスであり,テンプレートであり抽象クラスです。
class StringDisplayImpl は,環境側の具象クラスです。

◆◆Bridge パターンの実装◆◆

サンプルは,文字列の周りに飾りを付けるというごく簡単なものになっています。方法論としては十分なのかもしれません。

サンプルでは,環境側の具体的な動作を実装しているのは,クラス StringDisplayImpl の1つであり,機能側の機能は,メソッド display とメソッド multiDisplay の2つであります。 これらのクラスの動作はクライアント(ここではメインルーチンのこと)のクラスやメソッドの呼び方を見れば判ります。


def main():
    d1 = Display(StringDisplayImpl("Hello, Japan."))
    d2 = Display(StringDisplayImpl("Hello, World."))
    d3 = CountDisplay(StringDisplayImpl("Hello, Universe."))
    d1.display()
    d2.display()
    d3.display()
    d3.multiDisplay(5)

if __name__=='__main__':
    main()
"""標準出力
+-------------+
|Hello, Japan.|
+-------------+
+-------------+
|Hello, World.|
+-------------+
+----------------+
|Hello, Universe.|
+----------------+
+----------------+
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
+----------------+
"""

最初に,環境側の具象クラスをインスタンス化して,それをパラメータにして機能側のクラスをインスタンス化しています。そして,そのインスタンスを使って,機能側のメソッドを呼んでいます。

機能側のクラスのインスタンス化を次のようにしても同じ動作をします。厳格に言えば,クラス CountDisplay のメソッド multiDisplay を使いたいときだけそのクラスをインスタンス化すればよいわけです。


    d1 = CountDisplay(StringDisplayImpl("Hello, Japan."))
    d2 = CountDisplay(StringDisplayImpl("Hello, World."))
    d3 = CountDisplay(StringDisplayImpl("Hello, Universe."))

環境側にクラスが追加されたときは,呼んでいるクラス StringDisplayImpl だけを替えればよいです。機能側にメソッドが追加されたときは,呼んでいるクラス CountDisplay だけを替えればよいことになります。

実際に文字を出力しているのは,環境側の具象クラス StringDisplayImpl のメソッドです。機能側の上位クラス Display では,環境側の固有の動作を一般的な動作に置き換えています。そしてその一般的な動作を使ってそれをアレンジするメソッドを実装しています。機能側の追加クラスではそのアレンジするメソッドだけを改造しています。

さらに機能側の上位クラス Display では,環境側のインスタンスを受け取りそれを使って,呼んだクラスの固有の動作をするようになっています。誰から呼ばれたかは知らなくてもよいことになります。それはクライアント(ここではメインルーチンのこと)が知っていればよいことになります。

つまり,クラス Display が Bridge の役目を果たしているわけです。

このような構成になっているので,実際の応用では,機能側と環境側で独立して追加していけることになります。

◆◆ソースコード◆◆

このWebの左上隅からダウンロードできます。

ソースファイルは1つです。
・Bridge.py;Bridge パターンのPythonサンプル

【Bridge.py】
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
from abc import ABCMeta, abstractmethod

class Display(object):
    def __init__(self, impl):
        self.impl = impl
    def open(self):
        self.impl.rawOpen()
    def print(self):
        self.impl.rawPrint()
    def close(self):
        self.impl.rawClose()
    def display(self):
        self.open()
        self.print()
        self.close()

class CountDisplay(Display):
    def __init__(self, impl):
        super(CountDisplay, self).__init__(impl)
    def multiDisplay(self, times):
        self.open()
        for i in range(times):
            self.print()
        self.close()

class DisplayImpl(metaclass = ABCMeta):

    @abstractmethod
    def rawOpen(self):
        pass
    @abstractmethod
    def rawPrint(self):
        pass
    @abstractmethod
    def rawClose(self):
        pass

class StringDisplayImpl(DisplayImpl):
    def __init__(self, string):
        self.string = string
        self.width = len(string)
    def rawOpen(self):
        self.printLine()
    def rawPrint(self):
        sys.stdout.write("|{0}|\n".format(self.string))
    def rawClose(self):
        self.printLine()
    def printLine(self):
        sys.stdout.write("+{0}+\n".format("-"*self.width))

def main():
    d1 = Display(StringDisplayImpl("Hello, Japan."))
    d2 = Display(StringDisplayImpl("Hello, World."))
    d3 = CountDisplay(StringDisplayImpl("Hello, Universe."))
    d1.display()
    d2.display()
    d3.display()
    d3.multiDisplay(5)

if __name__=='__main__':
    main()
"""標準出力
+-------------+
|Hello, Japan.|
+-------------+
+-------------+
|Hello, World.|
+-------------+
+----------------+
|Hello, Universe.|
+----------------+
+----------------+
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
|Hello, Universe.|
+----------------+
"""

以上

トップページに戻る
inserted by FC2 system