GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。ただ、取り上げられている実例は、JAVAベースのため、自分の理解を深めるためにも、Pythonで同等のプラクティスに挑んでみました。
■ Decorator(デコレータ・パターン)
Decoratorパターン(デコレータ・パターン)とは、GoF(Gang of Four; 4人のギャングたち)によって定義されたデザインパターンの1つである。 このパターンは、既存のオブジェクトに新しい機能や振る舞いを動的に追加することを可能にする。
UML class and sequence diagram
UML class diagram
□ 備忘録
オブジェクトにどんどんデコレーション(飾り付け)を施していくようなデザインパターンとのことです。
スポンジケーキに、クリームを塗り、イチゴを載せればストロベリーショートケーキになるような感じです。
Pythonプログラミングに携わっていると、よくお目にかかるやつですね。
■ "Decorator"のサンプルプログラム
実際に、Decoratorパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。
- 文字列
Hello, world.
をそのまま表示する - 文字列
Hello, world.
の前後に、#
文字を差し込んで表示する - 文字列
Hello, world.
の前後に、#
文字を差し込んで、さらに、 枠線で囲んで表示する - 文字列
HELLO
を 枠線で囲んで、前後に、*
文字を差し込んで、さらに、 枠線で2回囲んで、、、(以下、略)
$ python Main.py
Hello, world.
# Hello, world.#
+---------------+
|#Hello, world.#|
+---------------+
/+-----------+/
/|+---------+|/
/||*+-----+*||/
/||*|HELLO|*||/
/||*+-----+*||/
/|+---------+|/
/+-----------+/
見た目、ハノイの塔みたいな表示になりました。
■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern/tree/master/Decorator/step1
- ディレクトリ構成
.
├── Main.py
└── decorator
├── __init__.py
├── border.py
└── display.py
(1) Componentの役
機能を追加するときの核に役です。Component
役は。スポンジケーキのインタフェースだけを定めます。
サンプルプログラムでは、Display
クラスが、この役を努めます。
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
クラスが、この役を努めます。
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
クラスが、この役を努めます。
from decorator.display import Display
class Border(Display):
def __init__(self, display):
self.display = display
(4) ConcreteDecorator(具体的な装飾者)の役
具体的なDecorator
の役です。
サンプルプログラムでは、SideBorder
クラスと、FullBorder
クラスが、この役を努めます。
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
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
メソッドが、この役を努めます。
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デコレータを活用してみる!) -その1-
Pythonプログラミングで、よく、デコレータを見かける機会が多いので、startMain
メソッドの部分をPythonデコレータで書き換えてみたいと思います。
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern/tree/master/Decorator/step2
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()
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()
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()
動かしてみます。
$ python Main.py
Hello, world.
# Hello, world.#
+---------------+
|#Hello, world.#|
+---------------+
# +-------------------+#
# |+-----------------+|#
# ||#+-------------+#||#
# ||#|Hello, world.|#||#
# ||#+-------------+#||#
# |+-----------------+|#
# +-------------------+#
ここでのやり方では、枠線の文字を#
と、文字列Hello, world.
を埋め込みにしてしまったので、プログラミングの柔軟性が損なってしまった結果になってしまいました。
□ 備忘録 (Pythonデコレータを活用してみる!) -その2-
枠線の文字や、文字列を適時、指定できるように、Pythonデコレータのやり方を、さらに改善してみます。
こちらのQiita記事「OpenStackを活用して、WSGI アプリケーションの仕組みを理解するを参考にしています。
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern/tree/master/Decorator/step3
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
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()
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()
動かしてみます。
$ python Main.py
Hello, world.
# Hello, world.#
+---------------+
|#Hello, world.#|
+---------------+
/+-----------+/
/|+---------+|/
/||*+-----+*||/
/||*|HELLO|*||/
/||*+-----+*||/
/|+---------+|/
/+-----------+/
最初の動作結果と同じになりましたので、これで、ひとまず、完成とします。