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

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

# ■ Decorator（デコレータ・パターン）
Decoratorパターン（デコレータ・パターン）とは、GoF（Gang of Four; 4人のギャングたち）によって定義されたデザインパターンの1つである。 このパターンは、既存のオブジェクトに新しい機能や振る舞いを動的に追加することを可能にする。

## UML class and sequence diagram
![W3sDesign_Decorator_Design_Pattern_UML.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/69b7e55e-6e1d-6c3f-0cc8-bae1553cf69e.jpeg)
## UML class diagram
![decorator.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103595/c0384c43-968d-50e2-0809-6b361d808341.png)
[(以上、ウィキペディア（Wikipedia）より引用)](https://ja.wikipedia.org/wiki/Decorator_パターン)

## □ 備忘録
オブジェクトにどんどんデコレーション（飾り付け）を施していくようなデザインパターンとのことです。
スポンジケーキに、クリームを塗り、イチゴを載せればストロベリーショートケーキになるような感じです。
Pythonプログラミングに携わっていると、よくお目にかかるやつですね。

# ■ "Decorator"のサンプルプログラム
実際に、Decoratorパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。

- 文字列`Hello, world.`をそのまま表示する
- 文字列`Hello, world.`の前後に、`#`文字を差し込んで表示する
- 文字列`Hello, world.`の前後に、`#`文字を差し込んで、さらに、 枠線で囲んで表示する
- 文字列`HELLO`を 枠線で囲んで、前後に、`*`文字を差し込んで、さらに、 枠線で２回囲んで、、、（以下、略）


```console
$ python Main.py 
Hello, world.
#Hello, world.#
+---------------+
|#Hello, world.#|
+---------------+

/+-----------+/
/|+---------+|/
/||*+-----+*||/
/||*|HELLO|*||/
/||*+-----+*||/
/|+---------+|/
/+-----------+/
```

見た目、ハノイの塔みたいな表示になりました。

# ■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern/tree/master/Decorator/step1

- ディレクトリ構成

```console
.
├── Main.py
└── decorator
    ├── __init__.py
    ├── border.py
    └── display.py
```
## (1) Componentの役
機能を追加するときの核に役です。`Component`役は。スポンジケーキのインタフェースだけを定めます。
サンプルプログラムでは、`Display`クラスが、この役を努めます。

```python:decorator/display.py
from abc import ABCMeta, abstractmethod

class Display(metaclass=ABCMeta):
    @abstractmethod
    def getColumns(self):
        pass

    @abstractmethod
    def getRows(self):
        pass

    @abstractmethod
    def getRowText(self, row):
        pass

    def show(self):
        for i in range(self.getRows()):
            print(self.getRowText(i))

... (snip)
```
## (2) ConcreteComponentの役
`Component`役のインタフェースを実装している具体的なスポンジケーキです。
サンプルプログラムでは、`StringDisplay`クラスが、この役を努めます。

```python:decorator/display.py
class StringDisplay(Display):
    def __init__(self, string):
        self.string = string

    def getColumns(self):
        return len(self.string)

    def getRows(self):
        return 1

    def getRowText(self, row):
        if row == 0:
            return self.string
        else:
            return None
```
## (3) Decorator（装飾者）の役
`Component`役と同じインタフェースを持ちます。そして、さらに、この`Decorator`役が飾る対象となる`Component`役を持っています。この役は、自分が飾っている対象を「知っている」わけです。
サンプルプログラムでは、`Border`クラスが、この役を努めます。

```python:decorator/border.py
from decorator.display import Display

class Border(Display):
    def __init__(self, display):
        self.display = display
```
## (4) ConcreteDecorator（具体的な装飾者）の役
具体的な`Decorator`の役です。
サンプルプログラムでは、`SideBorder`クラスと、`FullBorder`クラスが、この役を努めます。

```python:decorator/border.py
class SideBorder(Border):
    def __init__(self, display, ch):
        super(SideBorder, self).__init__(display)
        self.__borderChar = ch

    def getColumns(self):
        return 1 + self.display.getColumns() + 1

    def getRows(self):
        return self.display.getRows()

    def getRowText(self, row):
        return self.__borderChar + self.display.getRowText(row) + self.__borderChar
```
```python:decorator/border.py
class FullBorder(Border):
    def __init__(self, display):
        super(FullBorder, self).__init__(display)

    def getColumns(self):
        return 1 + self.display.getColumns() + 1

    def getRows(self):
        return 1 + self.display.getRows() + 1

    def getRowText(self, row):
        if row == 0:
            return '+' + self.__make_line('-', self.display.getColumns()) + '+'
        elif row == self.display.getRows() + 1:
            return '+' + self.__make_line('-', self.display.getColumns()) + '+'
        else:
            return '|' + self.display.getRowText(row - 1) + '|'

    def __make_line(self, char, count):
        buf = ''
        for _ in range(count):
            buf += char
        return buf
```
## (5) Client（依頼人）の役
サンプルプログラムでは、`startMain`メソッドが、この役を努めます。

```python:Main.py
from decorator.display import StringDisplay
from decorator.border import SideBorder, FullBorder

def startMain():
    b1 = StringDisplay('Hello, world.')
    b2 = SideBorder(b1, '#')
    b3 = FullBorder(b2)
    b1.show()
    b2.show()
    b3.show()
    print("")
    b4 = SideBorder(
        FullBorder(
            FullBorder(
                SideBorder(
                    FullBorder(
                        StringDisplay('HELLO')
                    ),
                    '*'
                )
            )
        ),
        '/'
    )
    b4.show()

if __name__ == '__main__':
    startMain()
```
## □ 備忘録 （Pythonデコレータを活用してみる！） -その１-
Pythonプログラミングで、よく、デコレータを見かける機会が多いので、`startMain`メソッドの部分を**Pythonデコレータ**で書き換えてみたいと思います。

Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern/tree/master/Decorator/step2

```python:decorator/display.py
from abc import ABCMeta, abstractmethod

className = []

class Display(metaclass=ABCMeta):
    @abstractmethod
    def getColumns(self):
        pass

    @abstractmethod
    def getRows(self):
        pass

    @abstractmethod
    def getRowText(self, row):
        pass

    def show(self):
        for i in range(self.getRows()):
            print(self.getRowText(i))

class StringDisplay(Display):
    def __init__(self, func):
        self.string = "Hello, world."
        self.func = func
        super(StringDisplay, self).__init__()

    def getColumns(self):
        return len(self.string)

    def getRows(self):
        return 1

    def getRowText(self, row):
        if row == 0:
            return self.string
        else:
            return None
    
    def __call__(self):
        global className
        className.append(self)
        return self.func()
```
```python:decorator/border.py
from decorator.display import Display, className

class Border(Display):
    def __init__(self, display):
        self.display = display
        super(Border, self).__init__()

class SideBorder(Border):
    def __init__(self, func):
        self.func = func
        self.__borderChar = '#'
        super(SideBorder, self).__init__(func)

    def getColumns(self):
        return 1 + self.display.getColumns() + 1

    def getRows(self):
        return self.display.getRows()

    def getRowText(self, row):
        return self.__borderChar + self.display.getRowText(row) + self.__borderChar
    
    def __call__(self):
        global className
        className.append(self)
        return self.func()

class FullBorder(Border):
    def __init__(self, func):
        self.func = func
        super(FullBorder, self).__init__(func)

    def getColumns(self):
        return 1 + self.display.getColumns() + 1

    def getRows(self):
        return 1 + self.display.getRows() + 1

    def getRowText(self, row):
        if row == 0:
            return '+' + self.__make_line('-', self.display.getColumns()) + '+'
        elif row == self.display.getRows() + 1:
            return '+' + self.__make_line('-', self.display.getColumns()) + '+'
        else:
            return '|' + self.display.getRowText(row - 1) + '|'

    def __make_line(self, char, count):
        buf = ''
        for _ in range(count):
            buf += char
        return buf

    def __call__(self):
        global className
        className.append(self)
        return self.func()
```
```python:Main.py
from decorator.display import StringDisplay, className
from decorator.border import FullBorder, SideBorder

def startMain():
    @FullBorder
    @SideBorder
    @StringDisplay
    def dummy1():
        className[2].show()
        className[1].show()
        className[0].show()
    dummy1()
    print("")
    className.clear()

    @SideBorder
    @FullBorder
    @FullBorder
    @SideBorder
    @FullBorder
    @StringDisplay
    def dummy2():
        className[0].show()
    dummy2()

if __name__ == '__main__':
    startMain()
```
動かしてみます。

```console
$ python Main.py 
Hello, world.
#Hello, world.#
+---------------+
|#Hello, world.#|
+---------------+

#+-------------------+#
#|+-----------------+|#
#||#+-------------+#||#
#||#|Hello, world.|#||#
#||#+-------------+#||#
#|+-----------------+|#
#+-------------------+#
```
ここでのやり方では、**枠線の文字を`#`と、文字列`Hello, world.`を埋め込みにしてしまったので、プログラミングの柔軟性が損なってしまった結果になってしまいました。**

## □ 備忘録 （Pythonデコレータを活用してみる！） -その2-
枠線の文字や、文字列を適時、指定できるように、Pythonデコレータのやり方を、さらに改善してみます。
こちらの[Qiita記事「OpenStackを活用して、WSGI アプリケーションの仕組みを理解する](https://qiita.com/ttsubo/items/0a1abecce272559edc94)を参考にしています。

Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern/tree/master/Decorator/step3

```python:decorator/display.py
from abc import ABCMeta, abstractmethod

className = []

class Display(metaclass=ABCMeta):
    @abstractmethod
    def getColumns(self):
        pass

    @abstractmethod
    def getRows(self):
        pass

    @abstractmethod
    def getRowText(self, row):
        pass

    def show(self):
        for i in range(self.getRows()):
            print(self.getRowText(i))

class StringDisplay(Display):
    def __init__(self, func, string):
        self.string = string
        self.func = func
        super(StringDisplay, self).__init__()

    def getColumns(self):
        return len(self.string)

    def getRows(self):
        return 1

    def getRowText(self, row):
        if row == 0:
            return self.string
        else:
            return None
    
    def __call__(self):
        global className
        className.append(self)
        return self.func()

def StringDisplay_filter(string):
    def filter(func):
        return StringDisplay(func, string)
    return filter
```
```python:decorator/border.py
from decorator.display import Display, className

class Border(Display):
    def __init__(self, display):
        self.display = display
        super(Border, self).__init__()

class SideBorder(Border):
    def __init__(self, func, ch):
        self.func = func
        self.__borderChar = ch
        super(SideBorder, self).__init__(func)

    def getColumns(self):
        return 1 + self.display.getColumns() + 1

    def getRows(self):
        return self.display.getRows()

    def getRowText(self, row):
        return self.__borderChar + self.display.getRowText(row) + self.__borderChar
    
    def __call__(self):
        global className
        className.append(self)
        return self.func()

def SideBorder_filter(ch):
    def filter(func):
        return SideBorder(func, ch)
    return filter

class FullBorder(Border):
    def __init__(self, func):
        self.func = func
        super(FullBorder, self).__init__(func)

    def getColumns(self):
        return 1 + self.display.getColumns() + 1

    def getRows(self):
        return 1 + self.display.getRows() + 1

    def getRowText(self, row):
        if row == 0:
            return '+' + self.__make_line('-', self.display.getColumns()) + '+'
        elif row == self.display.getRows() + 1:
            return '+' + self.__make_line('-', self.display.getColumns()) + '+'
        else:
            return '|' + self.display.getRowText(row - 1) + '|'

    def __make_line(self, char, count):
        buf = ''
        for _ in range(count):
            buf += char
        return buf

    def __call__(self):
        global className
        className.append(self)
        return self.func()
```
```python:Main.py
from decorator.display import StringDisplay_filter, className
from decorator.border import SideBorder_filter, FullBorder

def startMain():
    @FullBorder
    @SideBorder_filter('#')
    @StringDisplay_filter("Hello, world.")
    def dummy1():
        className[2].show()
        className[1].show()
        className[0].show()
    dummy1()
    print("")
    className.clear()

    @SideBorder_filter('/')
    @FullBorder
    @FullBorder
    @SideBorder_filter('*')
    @FullBorder
    @StringDisplay_filter("HELLO")
    def dummy2():
        className[0].show()
    dummy2()

if __name__ == '__main__':
    startMain()
```
動かしてみます。

```console
$ python Main.py 
Hello, world.
#Hello, world.#
+---------------+
|#Hello, world.#|
+---------------+

/+-----------+/
/|+---------+|/
/||*+-----+*||/
/||*|HELLO|*||/
/||*+-----+*||/
/|+---------+|/
/+-----------+/
```
最初の動作結果と同じになりましたので、これで、ひとまず、完成とします。

# ■ 参考URL
- [「Java言語で学ぶデザインパターン入門」を終えて(ない)](https://medium.com/since-i-want-to-start-blog-that-looks-like-men-do/java言語で学ぶデザインパターン入門-を終えて-ない-2cc9b34a30b2)
- [Qiita記事「デザインパターン（Design Pattern）#Facade](https://qiita.com/nirperm/items/9236663abc7e96736af4)