[PYTHON] I studied about design patterns (personal memo) Part 8 (Proxy pattern, Command pattern, Interpreter 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 I studied about design patterns (personal memo) Part 5 I studied about design patterns (personal memo) Part 6 I studied about design patterns (personal memo) Part 7

Proxy pattern

--Proxy: Proxy --The proxy object is entrusted with processing that can be done by someone other than the person himself / herself. --The principal object takes over and processes the processing that cannot be done by the proxy object.

Actually use

Subject

--Mr. Tanaka, who is negotiating with customers as a salesperson ――I received some questions from customers, but some of them could not be answered, so I took them home ――I checked with my boss, Mr. Suzuki, and answered the customer again. --Agent object: Mr. Tanaka --Personal object: Mr. Suzuki

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


class Sales(metaclass=ABCMeta):
    """Sales interface"""

    def __init__(self):
        pass

    @staticmethod
    @abstractmethod
    def question1():
        pass

    @staticmethod
    @abstractmethod
    def question2():
        pass

    @staticmethod
    @abstractmethod
    def question3():
        pass


class Suzuki(Sales):
    """Suzuki-san class (personal object)"""

    @staticmethod
    def question1():
        print("Answer 1")

    @staticmethod
    def question2():
        print("Answer 2")

    @staticmethod
    def question3():
        print("Answer 3")


class Tanaka(Sales):
    """Mr. Tanaka class (agent object)"""

    @staticmethod
    def question1():
        print("That is "Answer 1"")

    @staticmethod
    def question2():
        print("That is "Answer 2"")

    @staticmethod
    def question3():
        print("that is"")
        #I can't answer, so ask Mr. Suzuki
        Suzuki().question3()
        print("Will be")


class Client:
    """Customer class"""

    @staticmethod
    def main():
        #Question 1
        Tanaka().question1()

        #Question 2
        Tanaka().question2()

        #Question 3
        Tanaka().question3()


if __name__ == '__main__':
    c = Client()
    c.main()

That is "Answer 1"
That is "Answer 2"
that is"
Answer 3
Will be

Summary of Proxy patterns

Proxy.png

Command pattern

--Sending a request to an object is the same as calling a method on that object. --The content of the request is expressed by what kind of argument is passed to the method. --If you want to send various requests, you have to increase the number and types of arguments. --Make the request itself an object and pass that object as an argument. --A pattern that makes a request a Command object and allows you to use multiple combinations of them.

Actually use

Subject

――In the science class, I decided to conduct an experiment on saturated saline solution, "How many grams of salt dissolve in 100g of water?" The procedure is as follows.

Experiment to make saturated salt solution by adding 1 g of salt to 100 g of water

  1. Put 100g of water in the beaker
  2. Put 1g of salt in the beaker
  3. Stir
  4. If completely melted, return to 2
  5. If the salt remains undissolved, record the amount of water, the amount of salt, and the concentration at that time.

――We will also conduct an experiment, "How many g of water is needed to dissolve all 10 g of salt?" The procedure is as follows.

Experiment to make saturated salt solution by adding 10 g of water to 10 g of salt

  1. Put 10g of salt in the beaker
  2. Put 10g of water in the beaker
  3. Stir
  4. If it does not melt completely, go back to 2.
  5. When the salt is completely dissolved, record the amount of water, the amount of salt, and the concentration at that time.

――It is difficult for all the students to describe the experiment method, so I will prepare an experiment set that contains the experiment method and give it to the students to experiment.

# -*- coding:utf-8 -*-

ADD_SALT = 1  #When adding salt and stirring
ADD_WATER = 2  #When adding water and stirring


class Beaker:
    """Experimental set"""

    def __init__(self, water: float, salt: float):
        self._water = water
        self._salt = salt
        self._melted = False
        self.mix()

    def mix(self):
        """
Method to stir the solution
Set whether it melted or remained unmelted
The concentration of saturated saline solution at room temperature is about 26.4%
        """
        if self.get_density() < 26.4:
            self._melted = True
        else:
            self._melted = False

    def is_melted(self) -> bool:
        return self._melted

    def add_salt(self, salt: float):
        self._salt += salt

    def add_water(self, water: float):
        self._water += water

    def get_density(self):
        return (self._salt/(self._water + self._salt))*100

    def note(self):
        print(f"water:{self._water}g")
        print(f"Salt:{self._salt}g")
        print(f"concentration:{self.get_density()}%")

    def experiment(self, param: int):
        """Method to perform experiment"""
        if param == ADD_SALT:
            #When conducting an experiment to make a saturated saline solution by adding 1 g of salt at a time
            #Add salt while completely dissolved

            while self.is_melted():
                self.add_salt(1)  #Add 1g of salt
                self.mix()  #mix

            print("Experiment to add 1g of salt at a time")
            self.note()

        elif param == ADD_WATER:
            #When conducting an experiment to make a saturated saline solution by adding 10 g of water at a time
            #Add water while remaining undissolved

            while not self.is_melted():
                self.add_water(10)  #Add 10g of water
                self.mix()  #mix

            print("Experiment to add 10g of water at a time")
            self.note()


class Student:
    """Student to experiment"""

    def main(self):
        #Experiment to make saturated salt solution by adding 1 g of salt to 100 g of water
        Beaker(100, 0).experiment(ADD_SALT)

        #Experiment to make saturated salt solution by adding 10 g of water to 10 g of salt
        Beaker(0, 10).experiment(ADD_WATER)


if __name__ == '__main__':
    s = Student()
    s.main()

--If you want to do an additional experiment (an experiment to make 100g of saline solution with a concentration of 10%), you have to modify the method of conducting the experiment of the experiment set class.

MAKE_SALT_WATER = 3  #When making saline solution
# ...
class Beaker:
  # ...
  def experiment(self, param: int):
      """Method to perform experiment"""
      if param == ADD_SALT:
        # ...

      elif param == ADD_WATER:
        # ...

      elif param == MAKE_SALT_WATER:
          #Experiment to make saline solution
          self.mix()
          #Measure the concentration and write it in a notebook
          print("Experiment to make saline solution")
          self.note()
  # ...

--If you increase the number of experiment patterns, you will have to add if statements to the experiment set class, and you will have to increase the parameters, which is not extensible. --Stop expressing the contents of the experiment with int, and express the experiment itself with one Command object. --By giving the experiment content, that is, the Command object, a common interface, the experiment set class can execute the method that performs the common experiment regardless of the type of experiment content (Command object) received. ..

Experimental code


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


class Beaker:
    """Experimental set"""

    def __init__(self, water: float, salt: float):
        self._water = water
        self._salt = salt
        self._melted = False
        self.mix()

    def mix(self):
        """
Method to stir the solution
Set whether it melted or remained unmelted
The concentration of saturated saline solution at room temperature is about 26.4%
        """
        if self.get_density() < 26.4:
            self._melted = True
        else:
            self._melted = False

    def is_melted(self) -> bool:
        return self._melted

    def add_salt(self, salt: float):
        self._salt += salt

    def add_water(self, water: float):
        self._water += water

    def get_density(self):
        return (self._salt/(self._water + self._salt))*100

    def note(self):
        print(f"water:{self._water}g")
        print(f"Salt:{self._salt}g")
        print(f"concentration:{self.get_density()}%")

Common interface


class Command(metaclass=ABCMeta):
    """A superclass that provides a common interface for classes that represent experiment content"""

    def __init__(self):
        self._beaker = None

    def set_beaker(self, beaker: Beaker):
        self._beaker = beaker

    def execute(self):
        pass

Each experiment content class (Command object)


class AddSaltCommand(Command):
    """Command class for experiments in which 1 g of salt is added at a time"""

    def execute(self):
        while self._beaker.is_melted():
            self._beaker.add_salt(1)
            self._beaker.mix()

        print("Experiment to add 1g of salt at a time")
        self._beaker.note()


class AddWaterCommand(Command):
    """Command class for experiments in which 10 g of water is added at a time"""

    def execute(self):
        while not self._beaker.is_melted():
            self._beaker.add_water(10)
            self._beaker.mix()

        print("Experiment to add 10g of water at a time")
        self._beaker.note()


class MakeSaltWaterCommand(Command):
    """Command class for experiments to make saline solution"""

    def execute(self):
        self._beaker.mix()

        print("Experiment to make saline solution")
        self._beaker.note()

Student class


class Student:
    """Student to experiment"""

    def main(self):
        add_salt = AddSaltCommand()
        add_salt.set_beaker(Beaker(100, 0))  #Prepare a beaker containing 100g of water

        add_water = AddWaterCommand()
        add_water.set_beaker(Beaker(10, 10))  #Prepare a beaker containing 10 g of salt

        make_saltwater = MakeSaltWaterCommand()
        make_saltwater.set_beaker(Beaker(90, 10))  #Prepare a beaker containing 90g of water and 10g of salt.

        add_salt.execute()  #Experiment to make saturated salt solution by adding 1 g of salt to 100 g of water

        add_water.execute()  #Experiment to make saturated salt solution by adding 10 g of water to 10 g of salt

        make_saltwater.execute()  # 10%Experiment to make 100g of salt solution

--By applying the Command pattern, you can add various experiments without changing the source code of the experiment set. ――It is also possible to create a new experiment by combining existing experiment contents. --If you describe the execute method of the existing experiment content in the execute method of the new experiment content, when the new experiment content is executed, the existing experiment content will also be executed in the order described. ――Reusability is also high.

Summary of Command patterns

Command.png

Interpreter pattern

--Interpreter: Interpreter / explainer --There are times when you want to perform some processing based on the analysis results of the contents of a file written in some format. ――The Interpreter pattern is the most suitable pattern to realize the processing according to the procedure obtained by such "analysis result".

Actually use

Subject

――Think about how to make cup ramen --The process is as follows

  1. Put "powdered soup" in cup noodles
  2. Pour hot water
  3. Wait 3 minutes
  4. Add liquid soup --The syntax tree is as follows Interpreter_tree.png

--Try to extract "process" and "process target" from this syntax tree. --There are two categories of "processing": "add" and "wait for 3 minutes". --On the other hand, the items classified as "processed" are not only "powdered soup", "noodles", "hot water", and "liquid soup" but also "powdered soup plus noodles" and "powdered soup plus noodles". "Hot water added" and "powdered soup plus noodles plus hot water for 3 minutes" are also considered to be treated. --In this way, the "processing target" also includes the "processing result", so in order to identify the two, the Interpreter pattern has the same structure as the Composite pattern.

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


class Operand(metaclass=ABCMeta):
    """Interface representing the processing target"""
    @abstractmethod
    def get_operand_string(self):
        pass

--Class that represents "processing target" and "processing result" implements this interface

class Ingredient(Operand):
    """Class that represents the processing target"""

    def __init__(self, operand_string: str):
        self._operand_string = operand_string

    def get_operand_string(self) -> str:
        return self._operand_string


class Expression(Operand):
    """Class that represents the processing result"""

    def __init__(self, operator):
        """Takes an operator that represents the processing content as an argument"""
        self._operand_string = None
        self._operator = operator

    def get_operand_string(self):
        return self._operator.execute().get_operand_string()

--The interface and implementation class that represent the process are as follows.

class Operator(metaclass=ABCMeta):
    """Interface representing processing"""
    @abstractmethod
    def execute(self):
        pass
class Plus(Operator):
    """A class that represents the process of adding"""

    def __init__(self, operand1: Operand, operand2: Operand):
        self._operand1 = operand1
        self._operand2 = operand2

    def execute(self) -> Operand:
        return Ingredient(f"{self._operand1.get_operand_string()}When{self._operand2.get_operand_string()}Add")


class Wait(Operator):
    """A class that represents the process of "waiting""""

    def __init__(self, minute: int, operand: Operand):
        self._minute = minute
        self._operand = operand

    def execute(self) -> Operand:
        return Ingredient(f"{self._operand.get_operand_string()}To{self._minute}What was separated")

――When you execute it, it looks like this

if __name__ == '__main__':
    #Material
    material1 = Ingredient("noodles")
    material2 = Ingredient("Powder soup")
    material3 = Ingredient("hot water")
    material4 = Ingredient("Liquid soup")

    #Process
    #Add noodles and powdered soup
    step1 = Plus(material1, material2).execute()

    #Add hot water
    step2 = Plus(step1, material3).execute()

    #Wait 3 minutes
    step3 = Wait(3, step2).execute()

    #Add liquid soup
    step4 = Plus(step3, material4).execute()

    print(f"{step4.get_operand_string()}: That's cup ramen!")


Noodles and powdered soup added, hot water added for 3 minutes, and liquid soup added: That's cup ramen!

--In the Interpreter pattern, one grammatical rule is expressed by one class. --In the sample case, the processes such as "add" and "wait" are expressed in one class, and it is possible to execute the processes according to the analysis result of the syntax.

Summary of Interpreter patterns

Interpreter.png

in conclusion

――Finally it's all over ――It seems that you have to be very conscious of these in order to master them in actual business ――I found out that I needed to practice by reading it over and over and writing in different patterns. -** There is a long way to go before you can write good code ** ...

Recommended Posts

I studied about design patterns (personal memo) Part 8 (Proxy pattern, Command pattern, Interpreter pattern)
I studied about design patterns (personal memo) Part 3 (Prototype pattern, Builder pattern)
I studied about design patterns (personal memo) Part 5 (Composite pattern, Decorator pattern, Visitor pattern)
I studied about design patterns (personal memo) Part 4 (AbstractFactory pattern, Bridge pattern, Strategy 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 #Proxy
I studied about Systemd properly