LoginSignup
3
9

More than 3 years have passed since last update.

Pythonで、デザインパターン「Bridge」を学ぶ

Last updated at Posted at 2020-01-16

GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。ただ、取り上げられている実例は、JAVAベースのため、自分の理解を深めるためにも、Pythonで同等のプラクティスに挑んでみました。

■ Bridge(ブリッジ・パターン)

Bridgeパターン(ブリッジ・パターン)とは、GoF(Gang of Four; 4人のギャングたち)によって定義されたデザインパターンの1つである。 「橋渡し」のクラスを用意することによって、クラスを複数の方向に拡張させることを目的とする。

UML class and sequence diagram

W3sDesign_Bridge_Design_Pattern_UML.jpg

UML class diagram

bridge.png
(以上、ウィキペディア(Wikipedia)より引用)

□ 備忘録

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クラスが、この役を努めます。

bridge/function/display_func.py
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クラスが、この役を努めます。

bridge/function/display_count_func.py
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()
bridge/function/display_random_func.py
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クラスが、この役を努めます。

bridge/implement/display_impl.py
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クラスが、この役を努めます。

bridge/implement/display_string_impl.py
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))
bridge/implement/display_textfile_impl.py
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メソッドが、この役を努めます。

Main.py
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()

■ 参考URL

3
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
9