[PYTHON] I studied about design patterns (personal memo) Part 5 (Composite pattern, Decorator pattern, Visitor pattern)

Introduction

This article is a personal study memo. I am writing an article driven by the obsession that what I input must be output. I am writing this article on Qiita with the hope that someone who is familiar with it will be able to point out mistakes and give advice.

I live a working life as an engineer, but I haven't learned about design patterns properly, so I studied.

What is described here https://github.com/ck-fm0211/notes_desigh_pattern I'm uploading to.

Past log

I studied about design patterns (personal memo) Part 1 I studied about design patterns (personal memo) Part 2 I studied about design patterns (personal memo) Part 3 I studied about design patterns (personal memo) Part 4

Composite pattern

-The Composite pattern facilitates the handling of recursive structures by "identifying the container with the contents". --Example: File system --If you want to delete all the files and folders under a certain folder, it is convenient to be able to delete them in the same way without being aware of whether it is a file or a folder.

Actually use

Subject

--In the sample case, consider directories and files. --Try creating a class that represents files and directories without being aware of the Composite pattern.

class File:
    def __init__(self, name):
        self._name = name

    def remove(self):
        print("{}Was deleted".format(self._name))

--The Directory class, which represents a directory, manages the objects of the directories and files under it as a List object, and when the remove method is called, deletes all the objects held in the list and then removes itself. Shall be deleted

class Directory:
    def __init__(self, name):
        self._name = name
        self._list = []

    def add(self, arg):
        self._list.append(arg)

    def remove(self):
        itr = iter(self._list)

        i = 0

        while next(itr, None) is not None:

            obj = self._list[i]

            if isinstance(obj, File):
                obj.remove()
            elif isinstance(obj, Directory):
                obj.remove()
            else:
                print("Cannot be deleted")

            i += 1

        print("{}Was deleted".format(self._name))


if __name__ == "__main__":
    file1 = File("file1")
    file2 = File("file2")
    file3 = File("file3")
    file4 = File("file4")

    dir1 = Directory("dir1")
    dir1.add(file1)

    dir2 = Directory("dir2")
    dir2.add(file2)
    dir2.add(file3)

    dir1.add(dir2)
    dir1.add(file4)

    dir1.remove()

--There is no problem so far. It's annoying when the request "I want the directory to contain not only directories and files but also symbolic links" comes out. -In the Composite pattern, the contents of the container and the container are equated. Implement an interface that has a common container and contents for identification. --Make File and Directory implement a common interface DirectoryEntry

class DirectoryEntry(metaclass=ABCMeta):
    @abstractmethod
    def remove(self):
        pass

--Define only the remove method in the DirectoryEntry interface --Implement File class and Directory class in the form of implementing this.

class File(DirectoryEntry):
    def __init__(self, name):
        self._name = name

    def remove(self):
        print("{}Was deleted".format(self._name))


class Directory(DirectoryEntry):
    def __init__(self, name):
        self._name = name
        self._list = []

    def add(self, entry: DirectoryEntry):
        self._list.append(entry)

    def remove(self):
        itr = iter(self._list)

        i = 0

        while next(itr, None) is not None:

            obj = self._list[i]
            obj.remove()

            i += 1

        print("{}Was deleted".format(self._name))

--By making both the Directory class and the File class a class that implements the DirectoryEntry class, in the remove method of the Directory class, it does not matter whether the actual situation is an instance of the File class or the Directory class. Can also be treated as a DirectoryEntry object. ――By using the Composite pattern in this way, even if you need to add a SymbolicLink class, you can flexibly handle it. --SymbolicLink class should be implemented to implement DirectoryEntry interface.

class SymbolicLink(DirectoryEntry):
    def __init__(self, name):
        self._name = name

    def remove(self):
        print("{}Was deleted".format(self._name))

Summary of Composite patterns

Composite.png

Decorator pattern

--The Decorator pattern provides a more flexible way to expand functionality by equating the decorative frame with the contents. ――The Decorator pattern is an image of overlaying functions one by one. It is an image of putting a decoration with a certain function on the core.

Actually use

Subject

――At the ice cream shop, you can freely choose the toppings. The customer does not have to make toppings, and can select multiple toppings in layers. --Define the following interface as a common interface for ice cream.

class Icecream(metaclass=ABCMeta):

    @abstractmethod
    def get_name(self):
        pass

    @abstractmethod
    def how_sweet(self):
        pass

――As classes with these interfaces, vanilla ice cream class, green tea ice cream class, etc. are provided as follows.

class VanillaIcecream(Icecream):
    def get_name(self):
        return "Vanilla ice cream"

    def how_sweet(self):
        return "Vanilla flavor"


class GreenTeaIcecream(Icecream):
    def get_name(self):
        return "Green tea ice cream"

    def how_sweet(self):
        return "Matcha flavor"

--Consider adding toppings to these ice cream interface implementation classes. --Consider cashew nuts and sliced almonds as toppings. --Vanilla ice cream topped with cashew nuts and vanilla ice cream topped with sliced almonds are required. --Here, the name (return value of getName method) changes and the taste (return value of howSweet () method) does not change by adding toppings. --In order to meet these demands, a method of creating a cashew nut vanilla ice cream class can be considered to represent a vanilla ice cream topped with cashew nuts.

class CashewNutsVanillaIcecream(Icecream):
    def get_name(self):
        return "Cashew nut vanilla ice cream"

――This kind of "addition of functions using inheritance" becomes very fixed. ――For example, if you want an instance that represents green tea ice cream with cashew nuts, you need a green tea ice cream inheritance class. ――The Decorator pattern is effective when you want to flexibly add various functions in this way. --In the design using the Decorator pattern, prepare another class that has only the extension function part, give the instance variable of that class the instance to be extended, and implement the same interface as the extension target.

class CashewNutsToppingIcecream(Icecream):

    def __init__(self, ice: Icecream):
        self._ice = ice

    def get_name(self):
        name = "Cashew nuts"
        name += self._ice.get_name()
        return name

    def how_sweet(self):
        return self._ice.how_sweet()

--CashewNutsToppingIcecream class is a class that represents ice cream topped with cashew nuts. --This class implements the Icecream interface, and its getName () method returns the value obtained by getName () of its own instance variable ice (Icecream instance) plus the string "cashew nuts". Returns as. Also, in the howSweet () method, the return value of the howSweet () method of the instance variable ice is returned as it is. ――With this design, as shown below, vanilla ice cream topped with cashew nuts, green tea ice cream topped with cashew nuts, vanilla ice cream topped with sliced almonds, sliced almonds, and cashew nuts. It is possible to make toppings in various combinations such as vanilla ice cream topped with both.

ice1 = CashewNutsToppingIcecream(VanillaIcecream())  #Vanilla ice cream with cashew nut topping
ice2 = CashewNutsToppingIcecream(GreenTeaIcecream())  #Matcha ice cream with cashew nut topping

Summary of Decorator patterns

Decorator.png

Visitor pattern

--In the Visitor pattern, it is easy to add processing by describing "processing" in the Visitor object that is the visitor. --The Acceptor object to be processed must implement the accept (Visitor visitor) method that accepts the Visitor object. --Example ――If you want to do the "waterworks" of your house, call a "waterworks contractor" to your house, say "Thank you.", And leave the rest to us. ――In addition, you may call an electrician or a remodeling company. ――To these visitors, you say "Well, thank you" and leave most of the work to you. ――There may be some differences in how you leave it to us, but in the end, we will leave everything to the vendor. ――If a new service provider appears, each household does not need to change their attitude, just call the provider and say "Thank you." To receive the new service. be able to. --The Visitor pattern is a pattern that allows you to add processing without adding processing to the receiving side.

Actually use

Subject

--Take a home visit as an example. ――In each family, whether you are a teacher or an aunt in the neighborhood, when a visitor visits you, if you are not a stranger, say "Welcome" and accept it. ――At this time, consider each family as an Acceptor and the teacher as a Visitor by applying it to the Visitor pattern. --In the Visitor pattern, the Visitor visits the home, which is the target of the visit. The visited family accepts the teacher, saying, "Welcome." ――At this time, whether you are a newcomer or a veteran, there is no change in the side that accepts the teacher.

# -*- coding:utf-8 -*-
from abc import ABCMeta, abstractmethod


#Teacher class
class Teacher(metaclass=ABCMeta):

    def __init__(self, students):
        self._students = students

    @abstractmethod
    def visit(self, student_home):
        getattr(self, 'visit_' + student_home.__class__.__name__.lower())(student_home)

    @abstractmethod
    def get_student_list(self):
        return self._students


#Rookie teacher class
class RookieTeacher(Teacher):

    def __init__(self, students):
        super().__init__(students)

    def visit(self, student_home):
        print("Hello Teacher")
        super().visit(student_home)

    @staticmethod
    def visit_tanaka(tanaka):
        tanaka.praised_child()

    @staticmethod
    def visit_suzuki(suzuki):
        suzuki.reproved_child()

    def get_student_list(self):
        return self._students


#Home class
class Home(metaclass=ABCMeta):
    @staticmethod
    def praised_child():
        pass

    @staticmethod
    def reproved_child():
        pass


#Acceptance interface
class TeacherAcceptor(metaclass=ABCMeta):

    def accept(self, teacher: Teacher):
        pass


#Suzuki's home
class Suzuki(Home, TeacherAcceptor):
    @staticmethod
    def praised_child():
        print("Suzuki Mother: Oh, if you're a teacher, make a joke")

    @staticmethod
    def reproved_child():
        print("Suzuki Mother: That's only for my child ...")

    def accept(self, teacher: Teacher):
        teacher.visit(self.__class__)


#Mr. Tanaka's home
class Tanaka(Home, TeacherAcceptor):
    @staticmethod
    def praised_child():
        print("Tanaka Mother: Oh, if you're a teacher, make a joke")

    @staticmethod
    def reproved_child():
        print("Tanaka Mother: No way, that's only for my child ...")

    def accept(self, teacher: Teacher):
        teacher.visit(self.__class__)

if __name__ == "__main__":
    rt = RookieTeacher(["suzuki", "tanaka"])

    rt.visit(Suzuki())
    rt.visit(Tanaka())

――The accept method of each home calls the visit method of the teacher (visitor) to realize common processing.

Summary of Visitor patterns

Visitor.png

Impressions

――I think there is a better example ...

Recommended Posts

I studied about design patterns (personal memo) Part 5 (Composite pattern, Decorator pattern, Visitor pattern)
I studied about design patterns (personal memo) Part 3 (Prototype pattern, Builder pattern)
I studied about design patterns (personal memo) Part 4 (AbstractFactory pattern, Bridge pattern, Strategy pattern)
I studied about design patterns (personal memo) Part 8 (Proxy pattern, Command pattern, Interpreter pattern)
I studied about design patterns (personal memo) Part 7 (Observer pattern, Memento pattern, State pattern, Flyweight pattern)
I studied about design patterns (personal memo) Part 6 (Chain of Responsibility pattern, Facade pattern, Mediator pattern)
Design Pattern #Decorator
About the Visitor pattern
I studied about Systemd properly