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

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

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

## UML class and sequence diagram
![W3sDesign_Bridge_Design_Pattern_UML.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/84fdedfa-a747-fa1f-454e-513817f72afc.jpeg)
## UML class diagram
![bridge.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/1ae8a791-28d2-f93e-c3d6-3dcff1464760.png)
[(以上、ウィキペディア（Wikipedia）より引用)](https://ja.wikipedia.org/wiki/Bridge_パターン)

## □ 備忘録
Bridgeパターンは、**機能のクラス階層**と**実装のクラス階層**を橋渡しするらしいです。

### (1) 機能のクラス階層とは？
あるクラスに対して、新しい機能を追加したい場合、新しくサブクラスを定義した上で、メソッドを実装します。
既存のスーパークラスと、新たに定義したサブクラスの関係が、"機能のクラス階層"になります。
一般的には、以下のような関係を想定します。

- スーパークラスは基本的な機能を持っている
- サブクラスで新しい機能を追加する

### (2) 実装のクラス階層とは？
新しい実装を追加したい場合、抽象クラスから派生した具体的なサブクラスを定義した上で、メソッドを実装します。
既存の抽象クラスと、新たに派生した具体的なサブクラスの関係が、"実装のクラス階層"になります。
一般的には、以下のような関係を想定します。

- 抽象クラスでは、抽象メソッドによってインタフェースを規定している
- 派生したサブクラスは具象メソッドによってそのインタフェースを実装する

# ■ "Bridge"のサンプルプログラム
実際に、Bridgeパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。
**機能のクラス階層**と**実装のクラス階層**を橋渡しを想定したサンプルになります。

- `DisplayFunc`と、`DisplayStringImpl`の橋渡しを経て、文字列を表示する
- `DisplayCountFunc`と、`DisplayStringImpl`の橋渡しを経て、文字列を表示する
- `DisplayCountFunc`と、`DisplayStringImpl`の橋渡しを経て、文字列を表示する
- `DisplayRandomFunc`と、`DisplayStringImpl`の橋渡しを経て、文字列を5回繰り返して表示する
- `DisplayRandomFunc`と、`DisplayStringImpl`の橋渡しを経て、文字列をランダム回数繰り返して表示する
- `DisplayFunc`と、`DisplayTextfileImpl`の橋渡しを経て、テキストファイルの内容を表示する

```console
$ 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

- ディレクトリ構成

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

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

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

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

```python: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))
```
```python: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`メソッドが、この役を努めます。

```python: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
- [「Java言語で学ぶデザインパターン入門」を終えて(ない)](https://medium.com/since-i-want-to-start-blog-that-looks-like-men-do/java言語で学ぶデザインパターン入門-を終えて-ない-2cc9b34a30b2)
- [Bridgeパターン from "tachikawa844の日記"](https://tachikawa844.hatenadiary.org/entry/20090112/1231754336)