Apprenez le modèle de conception "Décorateur" avec Python

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.

■ Décorateur (motif décorateur)

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 W3sDesign_Decorator_Design_Pattern_UML.jpg UML class diagram decorator.png (Ce qui précède est cité sur Wikipedia)

□ Mémorandum

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.

■ Exemple de programme "Decorator"

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.

■ Détails de l'exemple de programme

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

(1) Rôle du composant

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)

(2) Rôle du composant béton

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

(3) Le rôle du décorateur

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

(4) Le rôle du décorateur en béton

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

(5) Le rôle du client

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()

□ Mémorandum (Essayez d'utiliser le décorateur Python!) -Partie 1-

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. .. ** **

□ Mémorandum (Essayez d'utiliser le décorateur Python!) -Partie 2-

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.

■ URL de référence

Recommended Posts

Apprenez le modèle de conception "Décorateur" avec Python
Apprenez le modèle de conception "Prototype" avec Python
Apprenez le modèle de conception "Builder" avec Python
Apprenez le modèle de conception "Flyweight" en Python
Apprenez le modèle de conception "Observer" en Python
Apprenez le modèle de conception "Memento" avec Python
Apprenez le modèle de conception "Proxy" en Python
Apprenez le modèle de conception "Commande" en Python
Apprenez le modèle de conception "Bridge" avec Python
Apprenez le modèle de conception "Mediator" avec Python
Apprenez le modèle de conception "Iterator" avec Python
Apprenez le modèle de conception «Stratégie» avec Python
Apprenez le modèle de conception "Composite" avec Python
Apprenez le modèle de conception "État" en Python
Apprenez le modèle de conception "Adapter" avec Python
Apprenez le modèle de conception "Abstract Factory" avec Python
Apprenez le modèle de conception "Méthode de modèle" en Python
Apprenez le modèle de conception «Chaîne de responsabilité» en Python
Apprenez le modèle de conception "Singleton" avec Python
Apprenez le modèle de conception "Façade" avec Python
Implémenter le modèle Singleton en Python
Motif singleton en Python
Modèle de visiteur en Python
Modèle de décorateur en Java
Trouver des erreurs en Python
Modèles de conception en Python: introduction
Python Design Pattern - Méthode de modèle
Python dans le navigateur: la recommandation de Brython
Enregistrez le fichier binaire en Python
Frappez l'API Sesami en Python
Obtenez le chemin du bureau en Python
[Python] Qu'est-ce que @? (À propos des décorateurs)
Obtenez le chemin du script en Python
Dans la commande python, python pointe vers python3.8
Accédez à l'API Web en Python
J'ai écrit la file d'attente en Python
Calculer le mois précédent en Python
Examiner la classe d'un objet avec python
Obtenez le chemin du bureau en Python
Obtenez le nom d'hôte en Python
Accéder à l'API Twitter avec Python
La première étape de Python Matplotlib
J'ai écrit la pile en Python
Maîtriser le module lowref en Python
Apprenez les bases de Python ① Débutants élémentaires
Essayez d'utiliser l'API Wunderlist en Python
Vérifiez le comportement du destroyer en Python
[Python Kivy] À propos de la modification du thème de conception
Essayez d'utiliser l'API Kraken avec Python
Apprenez les bases en touchant les variables python
Ecrire le test dans la docstring python
Prenez la somme logique de List en Python (fonction zip)
Modèle de conception du GoF à partir du problème 2. Structure
Afficher Python 3 dans le navigateur avec MAMP
Tweet à l'aide de l'API Twitter en Python
Decorator pour éviter UnicodeEncodeError dans Python 3 print ()
Vérifiez si l'URL existe en Python
Associez l'ensemble de tables dans les modèles de python.py