[PYTHON] I studied about design patterns (personal memo) Part 7 (Observer pattern, Memento pattern, State pattern, Flyweight 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

Observer pattern

――The purpose is to observe changes in the state, but rather the emphasis is on "notification" rather than "observation". --When the state of an instance changes, the instance itself "notifies" the change of state to the "observer". --Example --Think about an airplane boarding pass. --If the person who purchased the boarding pass of the plane is forced to cancel, contact the airline and tell them to cancel. ――It is the "airline" that manages each passenger, and the "passenger" that notifies the cancellation. --The "passenger" will contact the "airline" if he wants or no longer needs the ticket. --With such a mechanism, airlines do not have to constantly observe all users.

Actually use

Subject

--Boss and subordinates --Request multiple subordinates to create deliverables --Tell your boss the completion report

Observer_1.png

--The place to prepare the Observer interface is the key --In the Observer interface, define an update method that serves as a contact point for receiving notifications from the observation target when the observation target changes. --Create a Subject abstract class to be observed --Implement an add_observer method to freely add Observer instances and a notify_observers method to notify all of the Observer instances it holds.

Summary of Observer pattern

Observer_2.png

Memento pattern

--Memento: Souvenirs, keepsakes --By saving the state of the instance as a snapshot, it is possible to restore the state of the instance at that time. --The state of the instance may change steadily during program execution. --There may be requests such as "I want to return to the previous state" or "I want to return to the state at a certain point" for an instance that has changed once. --The Memento pattern makes it easy to take a snapshot of the state of an instance at a certain time, and even restore it from there. --Although clones may be created to remember the state of all instances, the Memento pattern considers keeping only the information you need and restoring only the data you need.

Actually use

Subject

--Addition ――Adding 1 to 5 ...

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


class Memento:
    """Memento class that keeps track of progress"""
    result = -1 #Represents the progress of the calculation

    def __init__(self, temp):
        """Constructor that receives the calculation progress as an argument"""
        Memento.result = temp


class Calc:
    """A class that represents a single calculation."""
    temp = 0

    @staticmethod
    def plus(plus):
        """Method to perform addition"""
        Calc.temp += plus

    @staticmethod
    def create_memento():
        """Method to get progress as Memento"""
        return Memento(Calc.temp)

    @staticmethod
    def set_memento(memento: Memento):
        """Get the calculation progress from Memento and set it to temp"""
        Calc.temp = memento.result

    @staticmethod
    def get_temp():
        """Method to get the calculation result"""
        return Calc.temp


class Calculator:

    def __init__(self):
        self.result_dict = {}

    def run(self):
        c = Calc()

        #1st calculation
        for i in range(1, 6):
            c.plus(i)

        print(f"Addition up to 5: {c.get_temp()}")
        self.result_dict["Addition up to 5"] = c.create_memento()

        #Second calculation
        c2 = Calc()
        c2.set_memento(self.result_dict["Addition up to 5"])

        #1st calculation
        for i in range(6, 11):
            c2.plus(i)

        print(f"Addition up to 10: {c2.get_temp()}")
        self.result_dict["Addition up to 10"] = c2.get_temp()


if __name__ == '__main__':
    c = Calculator()
    c.run()

――By leaving a certain stage as a "snapshot", you can quickly return to the state at that time. --In the Memento pattern, it is up to the Originator (here Calc) to decide what value should be left as the Memento. --Originator leaves information you think you need as Memento and restores state from Memento

Summary of Memento patterns

Memento.png

State pattern

--In object-oriented design, things are often represented as classes. --A pattern that expresses "state" as a class, not a thing

Actually use

Subject

--Conversation at work ――The way you assign tasks changes depending on the mood of your boss (I hate that kind of workplace ...) --When you are in a good mood

Subordinates: Good morning
Boss: Good morning
Subordinates: I'll do xxx today
Boss: Good, good luck

--When you are in a bad mood

Subordinates: Good morning
Boss: Oh
Subordinates: I'll do xxx today
Boss: Hey, did you say ooo? Did you do it?
Subordinate: (I haven't heard ...) I'm sorry, I'll do it soon! !! !! !! !! !! !! !!

--Cord

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


class Boss:
    STATE_ORDINARY = 0  #Normal boss
    STATE_IN_BAD_MOOD = 1   #Boss in a bad mood

    def __init__(self):
        self.state = -1  #Represents the state of the boss

    def change_state(self, state):
        """Change the status of your boss"""
        self.state = state

    def morning_greet(self):
        """Return the morning greeting"""
        if self.state == Boss.STATE_ORDINARY:
            return "Good morning"
        elif self.state == Boss.STATE_IN_BAD_MOOD:
            return "Oh"
        else:
            pass

    def assign_task(self):
        """Shake the task"""
        if self.state == Boss.STATE_ORDINARY:
            return "Like, do your best"
        elif self.state == Boss.STATE_IN_BAD_MOOD:
            return "Hey, did you say ooo? Did you do it?"
        else:
            pass

――Suppose that your boss's boss pointed out and you started to manage more properly → A new pattern was born ――It's not cool to modify the if branch --In the State pattern, prepare a class that represents the "state" and make this "state" interchangeable. --In the case of a sample case, first of all, "good mood diagonal state" and "normal state" are required. --The State can be changed by any class, but in this case, it will be changed from somewhere inside.

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


class State(metaclass=ABCMeta):

    @staticmethod
    def morning_greet():
        """Morning greeting"""
        pass

    @staticmethod
    def assign_task():
        """Shake the task"""
        pass


class BadMoodState(State):

    @staticmethod
    def morning_greet():
        return "Oh"

    @staticmethod
    def assign_task():
        return "Hey, did you say ooo? Did you do it?"


class OrdinaryState(State):

    @staticmethod
    def morning_greet():
        return "Good morning"

    @staticmethod
    def assign_task():
        return "Like, do your best"


class StatePatternBoss:

    def __init__(self):
        self.state = None

    def change_state(self, state: State):
        self.state = state

    def morning_greet(self):
        return self.state.morning_greet()

    def assign_task(self):
        return self.state.assign_task()


if __name__ == '__main__':

    boss_state = StatePatternBoss()

    print("=====Day 1: Good mood=====")
    boss_state.change_state(OrdinaryState())
    print("Subordinates: Good morning")
    print(f"boss:{boss_state.morning_greet()}")
    print("Subordinates: I'll do xxx today")
    print(f"boss:{boss_state.assign_task()}")

    print("=====Day 2: Bad mood=====")
    boss_state.change_state(BadMoodState())
    print("Subordinates: Good morning")
    print(f"boss:{boss_state.morning_greet()}")
    print("Subordinates: I'll do xxx today")
    print(f"boss:{boss_state.assign_task()}")

=====Day 1: Good mood=====
Subordinates: Good morning
Boss: Good morning
Subordinates: I'll do xxx today
Boss: Good, good luck
=====Day 2: Bad mood=====
Subordinates: Good morning
Boss: Oh
Subordinates: I'll do xxx today
Boss: Hey, did you say ooo? Did you do it?

――By doing this, it is easy to respond even if the mood pattern increases

(Omission)
class GoodMoodState(State):

    @staticmethod
    def morning_greet():
        return "Good morning! Let's do our best today!"

    @staticmethod
    def assign_task():
        return "How nice! The other ooo was also nice! Good luck with this condition!"
(Omitted)
    print("=====Day 3: Good mood=====")
    boss_state.change_state(GoodMoodState())    #Just change here
    print("Subordinates: Good morning")
    print(f"boss:{boss_state.morning_greet()}")
    print("Subordinates: I'll do xxx today")
    print(f"boss:{boss_state.assign_task()}")
=====Day 3: Good mood=====
Subordinates: Good morning
Boss: Good morning! Let's do our best today!
Subordinates: I'll do xxx today
Boss: Like! The other ooo was also nice! Good luck with this condition!

Summary of State patterns

State.png

Flyweight pattern

--Flyweight: means "flyweight" in English --Patterns focused on wasting resources by sharing instances --Example: The small image used for the background of the homepage is not exchanged over the network as many times as it is displayed in the background, but usually the image is acquired once and "the image" is displayed side by side. --A pattern that aims to lighten the entire program by sharing the same instance so as not to create useless instances.

Actually use

Subject

--Try to create a message using "human characters" ―― "Ai is better than blue" ――Make each character with "human characters", take a picture from the roof, and leave it in the picture. --If the class that represents a single human character is the HumanLetter class, the class that generates a message with human characters is as follows.

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


class HumanLetter:

    def __init__(self, letter):
        self._letter = letter

    def get_letter(self):
        return self._letter


class Main:

    @staticmethod
    def take_a_photo(letter: HumanLetter):
        """Take a photo"""
        print(letter.get_letter())

    def main(self):
        """Create a letter"""
        a = HumanLetter("Ah")
        self.take_a_photo(a)

        i = HumanLetter("I")
        self.take_a_photo(i)

        ha = HumanLetter("Is")
        self.take_a_photo(ha)

        a2 = HumanLetter("Ah")
        self.take_a_photo(a2)

        o = HumanLetter("O")
        self.take_a_photo(o)

        yo = HumanLetter("Yo")
        self.take_a_photo(yo)

        ri = HumanLetter("Ri")
        self.take_a_photo(ri)

        mo = HumanLetter("Also")
        self.take_a_photo(mo)

        a3 = HumanLetter("Ah")
        self.take_a_photo(a3)

        o2 = HumanLetter("O")
        self.take_a_photo(o2)

        i2 = HumanLetter("I")
        self.take_a_photo(i2)


if __name__ == '__main__':
    h = Main()
    h.main()

――The same character appears many times. ――If you just take a picture, you can reuse it ...

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


class HumanLetter:

    def __init__(self, letter):
        self._letter = letter

    def get_letter(self):
        return self._letter


class Main:

    @staticmethod
    def take_a_photo(letter: HumanLetter):
        """Take a photo"""
        print(letter.get_letter())

    def main(self):
        """Create a letter"""
        a = HumanLetter("Ah")
        self.take_a_photo(a)

        i = HumanLetter("I")
        self.take_a_photo(i)

        ha = HumanLetter("Is")
        self.take_a_photo(ha)

        self.take_a_photo(a)

        o = HumanLetter("O")
        self.take_a_photo(o)

        yo = HumanLetter("Yo")
        self.take_a_photo(yo)

        ri = HumanLetter("Ri")
        self.take_a_photo(ri)

        mo = HumanLetter("Also")
        self.take_a_photo(mo)

        self.take_a_photo(a)

        self.take_a_photo(o)

        self.take_a_photo(i)


if __name__ == '__main__':
    h = Main()
    h.main()

--I was able to reduce the number of costly instantiations (here, a constructor that sorts people to form characters. Originally, DB access etc. can be considered). Naturally, the number of instances is also decreasing. --Further reduction with the Flyweight pattern --Create a Factory class that creates and manages instances that should be lightened, and get instances that should be lightened through this Factory class.

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


class HumanLetter:

    def __init__(self, letter):
        self._letter = letter

    def get_letter(self):
        return self._letter


class HumanLetterFactory:
    __singleton = None
    __human_letter = None

    def __new__(cls, *args, **kwargs):
        if cls.__singleton is None:
            cls.__singleton = super(HumanLetterFactory, cls).__new__(cls)
        return cls.__singleton

    def __init__(self):
        self.dic = {}

    def get_human_letter(self, letter: str):

        try:
            human_letter = self.dic[letter]
        except KeyError:
            human_letter = HumanLetter(letter)
            self.dic[letter] = human_letter

        return human_letter



class Main:

    @staticmethod
    def take_a_photo(letter: HumanLetter):
        """Take a photo"""
        print(letter.get_letter())

    def main(self):
        """Create a letter"""

        #Create singleton
        hlf = HumanLetterFactory()

        a = hlf.get_human_letter("Ah")
        self.take_a_photo(a)
        print(a)

        i = hlf.get_human_letter("I")
        self.take_a_photo(i)
        print(i)

        ha = hlf.get_human_letter("Is")
        self.take_a_photo(ha)

        a2 = hlf.get_human_letter("Ah")
        self.take_a_photo(a2)
        print(a2)

        o = hlf.get_human_letter("O")
        self.take_a_photo(o)
        print(o)

        yo = hlf.get_human_letter("Yo")
        self.take_a_photo(yo)

        ri = hlf.get_human_letter("Ri")
        self.take_a_photo(ri)

        mo = hlf.get_human_letter("Also")
        self.take_a_photo(mo)

        a3 = hlf.get_human_letter("Ah")
        self.take_a_photo(a3)
        print(a3)

        o2 = hlf.get_human_letter("O")
        self.take_a_photo(o2)
        print(o2)

        i2 = hlf.get_human_letter("I")
        self.take_a_photo(i2)
        print(i2)


if __name__ == '__main__':
    h = Main()
    h.main()

Ah
<__main__.HumanLetter object at 0x1039c4da0> #It is a common instance in "A"
I
<__main__.HumanLetter object at 0x1039c4dd8>
Is
Ah
<__main__.HumanLetter object at 0x1039c4da0>  #It is a common instance in "A"
O
<__main__.HumanLetter object at 0x1039c4e48>
Yo
Ri
Also
Ah
<__main__.HumanLetter object at 0x1039c4da0> #It is a common instance in "A"
O
<__main__.HumanLetter object at 0x1039c4e48>
I
<__main__.HumanLetter object at 0x1039c4dd8>

--By setting the Factory class to Singleton, you can prevent multiple Factory classes from being generated. - Singleton --The get_human_letter method of the HumanLetterFactory class queries the managed dict, and if you need an instance that represents a character you already have, you don't bother to recreate it. --If you are asked for a character that you do not have, instantiate it newly, register it in the map, and return the generated instance. --By using the Flyweight pattern, it is not necessary for the user (caller such as Main class) to know which instance they have. In addition, it is possible to respond to requests from a plurality of callees without waste.

Summary of Flyweight pattern

Flyweight.png

Recommended Posts

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 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 8 (Proxy pattern, Command pattern, Interpreter pattern)
I studied about design patterns (personal memo) Part 6 (Chain of Responsibility pattern, Facade pattern, Mediator 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 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 #Builder
I wrote a design pattern in kotlin Prototype
I studied about Systemd properly
Design Pattern #Observer