En tant que matériel d'apprentissage des modèles de conception du GoF, le livre «Introduction aux modèles de conception appris dans le langage Java augmenté et révisé» semble être utile. Cependant, comme les exemples repris sont basés sur JAVA, j'ai essayé la même pratique avec Python pour approfondir ma compréhension.
Le modèle Decorator est l'un des modèles de conception définis par GoF (Gang of Four; 4 gangs). Ce modèle vous permet d'ajouter dynamiquement de nouvelles fonctionnalités et comportements à des objets existants.
UML class and sequence diagram UML class diagram (Ce qui précède est cité sur Wikipedia)
C'est un modèle de conception qui rend les objets de plus en plus décorés. Si vous appliquez de la crème sur un gâteau éponge et mettez de la fraise dessus, ce sera comme un gâteau court aux fraises. Vous les voyez souvent lorsque vous êtes impliqué dans la programmation Python.
Je voudrais exécuter un exemple de programme qui utilise le modèle Decorator et vérifier le comportement suivant.
--Affichez la chaîne de caractères «Hello, world.» Telle quelle
--Insérez le caractère #
avant et après la chaîne de caractères Hello, world.
pour l'afficher.
--Insérez le caractère #
avant et après la chaîne de caractères Hello, world.
, et affichez-le en l'entourant d'un cadre.
--Enclenchez la chaîne de caractères HELLO
avec un cadre, insérez les caractères *
avant et après, puis placez-la deux fois avec un cadre ... (ci-après, omis)
$ python Main.py
Hello, world.
#Hello, world.#
+---------------+
|#Hello, world.#|
+---------------+
/+-----------+/
/|+---------+|/
/||*+-----+*||/
/||*|HELLO|*||/
/||*+-----+*||/
/|+---------+|/
/+-----------+/
Cela ressemble à une tour à Hanoi.
Un code similaire a été téléchargé dans le référentiel Git. https://github.com/ttsubo/study_of_design_pattern/tree/master/Decorator/step1
.
├── Main.py
└── decorator
├── __init__.py
├── border.py
└── display.py
C'est un noyau lors de l'ajout de fonctions. Quel est le rôle de «Component»? Définissez uniquement l'interface de la génoise.
Dans l'exemple de programme, la classe Display
remplit ce rôle.
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)
C'est une génoise en béton qui implémente l'interface du rôle de «composant».
Dans l'exemple de programme, la classe StringDisplay
remplit ce rôle.
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
Il a la même interface que le rôle Component
. Et, en plus, il a le rôle de "Composant" que ce rôle de "Décorateur" est de décorer. Ce rôle "sait" ce que vous décorez.
Dans l'exemple de programme, la classe Border
remplit ce rôle.
decorator/border.py
from decorator.display import Display
class Border(Display):
def __init__(self, display):
self.display = display
C'est un rôle spécifique de «décorateur». Dans l'exemple de programme, les classes «SideBorder» et «FullBorder» remplissent ce rôle.
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
Dans l'exemple de programme, la méthode startMain
remplit ce rôle.
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()
Dans la programmation Python, je vois souvent des décorateurs, donc je voudrais réécrire la partie méthode startMain
avec ** Python decorator **.
Un code similaire a été téléchargé dans le référentiel Git. 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()
Je vais le déplacer.
$ python Main.py
Hello, world.
#Hello, world.#
+---------------+
|#Hello, world.#|
+---------------+
#+-------------------+#
#|+-----------------+|#
#||#+-------------+#||#
#||#|Hello, world.|#||#
#||#+-------------+#||#
#|+-----------------+|#
#+-------------------+#
Dans cette méthode, ** le caractère de bordure est incorporé avec «#» et la chaîne de caractères «Hello, world» est incorporée, ce qui entraîne une perte de flexibilité de programmation. .. ** **
Améliorons le fonctionnement des décorateurs Python afin que vous puissiez spécifier des caractères de bordure et des chaînes de caractères en temps opportun. Je me réfère à cet [article Qiita «Comprendre comment les applications WSGI fonctionnent avec OpenStack» (https://qiita.com/ttsubo/items/0a1abecce272559edc94).
Un code similaire a été téléchargé dans le référentiel Git. 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()
Je vais le déplacer.
$ python Main.py
Hello, world.
#Hello, world.#
+---------------+
|#Hello, world.#|
+---------------+
/+-----------+/
/|+---------+|/
/||*+-----+*||/
/||*|HELLO|*||/
/||*+-----+*||/
/|+---------+|/
/+-----------+/
Puisqu'il est identique au résultat de la première opération, il est terminé pour le moment.
Recommended Posts