Learn the design pattern "Memento" in Python

As a material for learning GoF design patterns, the book "Introduction to Design Patterns Learned in the Augmented and Revised Java Language" seems to be helpful. However, since the examples taken up are based on JAVA, I tried the same practice in Python to deepen my understanding.

■ Memento (Memento pattern)

The English word "Memento" means "keepsake / memorial". This pattern is a pattern that provides a device to remember (save) the state of an object at any point in time and later return the object to that state. (The state can be restored without destroying the encapsulation) In other words, a pattern for providing the "undo" (cancel the operation and return to the state before the operation) function as implemented in a text editor etc. is. It should be noted that only the minimum necessary information (field value) to restore the state is saved. (The above is quoted from "Technical support site for IT engineers by IT Senka")

UML class and sequence diagram W3sDesign_Memento_Design_Pattern_UML.jpg (The above is quoted from Wikipedia)

■ "Memento" sample program

Actually, I would like to run a sample program that utilizes the Memento pattern and check the following behavior. In addition, the sample program ** "Dice game for collecting fruits" ** assumes the following operations.

--This game will proceed automatically ――The main character of the game rolls the dice, and the action is decided according to the dice roll. --Displays the current status of each game (money you have, fruits you have) --At the start of the game, start with a possession of 100 yen -** If the current amount of money exceeds the amount of money you have saved, save the situation (the amount of money you have and the "delicious fruit" you have) ** -** If the current amount of money is less than half of the amount of money you have saved, restore the previously saved situation (money, "delicious fruit" you have) as the current situation. Masu ** ――It will end when you run out of money. --Repeat the game up to 100 times


Movement according to the dice

  1. When the dice roll "1" </ font>, your money will increase ** 100 yen **
  2. When the dice roll "2" </ font>, your money will be ** half ** (rounded down)
  3. When the dice roll "6" </ font>, you will get ** fruit ** (You can get normal ** "fruit" **, but you can get ** "delicious fruit" **, the probability is 50%)
  4. If you roll any other dice, nothing happens
$ python Main.py 
==== 0
Current status:[money = 100, fruits = []]
I have more money
The amount of money I have is now 200 yen
      (It has increased a lot, so let's save the current state)

==== 1
Current status:[money = 200, fruits = []]
fruits(Delicious grapes)I got
The amount of money I have is now 200 yen

==== 2
Current status:[money = 200, fruits = ['Delicious grapes']]
Nothing happened
The amount of money I have is now 200 yen

==== 3
Current status:[money = 200, fruits = ['Delicious grapes']]
I have more money
The amount of money I have is now 300 yen
      (It has increased a lot, so let's save the current state)


...(snip)


==== 22
Current status:[money = 500, fruits = ['Delicious grapes', 'Delicious grapes', 'Delicious banana', 'Delicious apple', 'Apple', 'banana']]
fruits(Delicious banana)I got
The amount of money I have is now 500 yen

==== 23
Current status:[money = 500, fruits = ['Delicious grapes', 'Delicious grapes', 'Delicious banana', 'Delicious apple', 'Apple', 'banana', 'Delicious banana']]
I have more money
The amount of money I have is now 600 yen
      (It has increased a lot, so let's save the current state)

==== 24
Current status:[money = 600, fruits = ['Delicious grapes', 'Delicious grapes', 'Delicious banana', 'Delicious apple', 'Apple', 'banana', 'Delicious banana']]
Your money has been halved
The amount of money I have is now 300 yen

==== 25
Current status:[money = 300, fruits = ['Delicious grapes', 'Delicious grapes', 'Delicious banana', 'Delicious apple', 'Apple', 'banana', 'Delicious banana']]
Nothing happened
The amount of money I have is now 300 yen

==== 26
Current status:[money = 300, fruits = ['Delicious grapes', 'Delicious grapes', 'Delicious banana', 'Delicious apple', 'Apple', 'banana', 'Delicious banana']]
Your money has been halved
The amount of money I have is now 150 yen
      (It has decreased a lot, so let's return to the previous state)

==== 27
Current status:[money = 600, fruits = ['Delicious grapes', 'Delicious grapes', 'Delicious banana', 'Delicious apple', 'Delicious banana']]
Your money has been halved
The amount of money I have is now 300 yen

...(snip)

At the end, I was able to confirm the operation using the Memento pattern.

■ Details of sample program

Similar code has been uploaded to the Git repository. https://github.com/ttsubo/study_of_design_pattern/tree/master/Memento

--Directory structure

.
├── Main.py
└── memento
    ├── __init__.py
    ├── gamer.py
    └── memento.py

(1) The role of Originator

The ʻOriginator role creates a Memento role when you want to save your current state. The ʻOriginator role also, when passed the previous Memento role, returns to the state it was in when the Memento role was created. In the sample program, the Gamer class serves this role.

memento/gamer.py


import random
from memento.memento import Memento

class Gamer(object):
    def __init__(self, money):
        self.__fruitname = ["Apple", "Grape", "banana", "Mandarin orange"]
        self.__money = money
        self.__fruits = []

    def getMoney(self):
        return self.__money

    def bet(self):
        dice = random.randint(1, 6)
        if dice == 1:
            self.__money += 100
            print("I have more money")
        elif dice == 2:
            self.__money //= 2
            print("Your money has been halved")
        elif dice == 6:
            f = self.__getFruit()
            print("fruits({0})I got".format(f))
            self.__fruits.append(f)
        else:
            print("Nothing happened")

    def createMemento(self):
        m = Memento(self.__money)
        for f in self.__fruits:
            if f.startswith("Delicious"):
                m.addFruit(f)
        return m

    def restoreMemento(self, memento):
        self.__money = memento.money
        self.__fruits = memento.getFruits()

    def __str__(self):
        return "[money = {0}, fruits = {1}]".format(self.__money, self.__fruits)

    def __getFruit(self):
        prefix = ''
        if bool(random.getrandbits(1)):
            prefix = "Delicious"
        return prefix + random.choice(self.__fruitname)

(2) The role of Memento (souvenir)

The Memento role summarizes the internal information of the ʻOriginatorrole. TheMemento role has internal information about the ʻOriginator role, but that information is not disclosed to everyone. In the sample program, the Memento class serves this role.

memento/memento.py


class Memento(object):
    def __init__(self, money):
        self.money = money
        self.fruits = []

    def getMoney(self):
        return self.money

    def addFruit(self, fruit):
        self.fruits.append(fruit)

    def getFruits(self):
        return self.fruits

(3) The role of Caretaker

The Caretaker role tells the ʻOriginator role when you want to save the current state of the ʻOriginator role. The ʻOriginatorrole receives it, creates aMemento role, and passes it to the Caretakerrole. TheCaretaker role saves the Mementorole for future needs. In the sample program, thestartMain` method serves this role.

Main.py


import time
from memento.gamer import Gamer

def startMain():
    gamer = Gamer(100)
    memento = gamer.createMemento()

    for i in range(100):
        print("==== {0}".format(i))
        print("Current status:{0}".format(gamer))
        gamer.bet()
        print("The money you have{0}It became a circle".format(gamer.getMoney()))

        if gamer.getMoney() > memento.getMoney():
            print("      (It has increased a lot, so let's save the current state)")
            memento = gamer.createMemento()
        elif gamer.getMoney() < memento.getMoney() / 2:
            print("      (It has decreased a lot, so let's return to the previous state)")
            gamer.restoreMemento(memento)

        time.sleep(1)
        print("")

if __name__ == '__main__':
    startMain()

■ Reference URL

-[Finishing "Introduction to Design Patterns Learned in Java Language" (Not)](https://medium.com/since-i-want-to-start-blog-that-looks-like-men-do/java Introduction to Design Patterns Learned in Language-Finishing-Not-2cc9b34a30b2)

Recommended Posts