As a material for learning GoF design patterns, the book "Introduction to Design Patterns Learned in the Augmented and Revised Java Language" seems to be helpful. However, since the examples taken up are based on JAVA, I tried the same practice in Python to deepen my understanding.
The Decorator pattern is one of the design patterns defined by GoF (Gang of Four; 4 gangs). This pattern allows you to dynamically add new features and behaviors to existing objects.
UML class and sequence diagram UML class diagram (Quoted from Wikipedia)
It is a design pattern that makes objects more and more decorated. If you apply cream to a sponge cake and put strawberries on it, it will be like a strawberry shortcake. You often see it when you're involved in Python programming.
I'd like to actually run a sample program that utilizes the Decorator pattern and see how it works:
--Display the string Hello, world.
as it is
--Insert the #
character before and after the character string Hello, world.
to display it.
--Insert the #
character before and after the character string Hello, world.
, and display it by surrounding it with a frame.
--Enclose the character string HELLO
with a frame, insert*
characters before and after it, and then enclose it twice with a frame ... (Hereafter, omitted)
$ python Main.py
Hello, world.
#Hello, world.#
+---------------+
|#Hello, world.#|
+---------------+
/+-----------+/
/|+---------+|/
/||*+-----+*||/
/||*|HELLO|*||/
/||*+-----+*||/
/|+---------+|/
/+-----------+/
It looks like a tower of Hanoi.
Similar code has been uploaded to the Git repository. https://github.com/ttsubo/study_of_design_pattern/tree/master/Decorator/step1
--Directory structure
.
├── Main.py
└── decorator
├── __init__.py
├── border.py
└── display.py
It is a core when adding functions. What is the role of Component
? Only define the interface of the sponge cake.
In the sample program, the Display
class serves this role.
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)
A concrete sponge cake that implements the interface for the role of Component
.
In the sample program, the StringDisplay
class serves this role.
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
It has the same interface as the Component
role. And, in addition, it has the Component
role that this Decorator
role decorates. This role "knows" what you are decorating.
In the sample program, the Border
class serves this role.
decorator/border.py
from decorator.display import Display
class Border(Display):
def __init__(self, display):
self.display = display
It's a specific role of Decorator
.
In the sample program, the SideBorder
and FullBorder
classes serve this role.
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
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
In the sample program, the startMain
method serves this role.
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()
In Python programming, I often see decorators, so I'd like to rewrite the startMain
method part with a ** Python decorator **.
Similar code has been uploaded to the Git repository. https://github.com/ttsubo/study_of_design_pattern/tree/master/Decorator/step2
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()
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()
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()
I will move it.
$ python Main.py
Hello, world.
#Hello, world.#
+---------------+
|#Hello, world.#|
+---------------+
#+-------------------+#
#|+-----------------+|#
#||#+-------------+#||#
#||#|Hello, world.|#||#
#||#+-------------+#||#
#|+-----------------+|#
#+-------------------+#
In this method, ** the border character is embedded with #
and the character string Hello, world.
is embedded, which results in a loss of programming flexibility. .. ** **
Let's improve the way the Python decorator works so that you can specify border characters and strings in a timely manner. I refer to this [Qiita article "Understanding how WSGI applications work with OpenStack" (https://qiita.com/ttsubo/items/0a1abecce272559edc94).
Similar code has been uploaded to the Git repository. https://github.com/ttsubo/study_of_design_pattern/tree/master/Decorator/step3
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
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()
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()
I will move it.
$ python Main.py
Hello, world.
#Hello, world.#
+---------------+
|#Hello, world.#|
+---------------+
/+-----------+/
/|+---------+|/
/||*+-----+*||/
/||*|HELLO|*||/
/||*+-----+*||/
/|+---------+|/
/+-----------+/
Since it is the same as the first operation result, it is completed for the time being.
-[Finishing "Introduction to Design Patterns Learned in Java Language" (Not)](https://medium.com/since-i-want-to-start-blog-that-looks-like-men-do/java Introduction to Design Patterns Learned in Language-Finishing-Not-2cc9b34a30b2) -Qiita article "Design Pattern #Facade
Recommended Posts