[Design Patterns - Study Notes] 23 Design Patterns - Memento Mode Memento (principle explanation + application scenario introduction + case introduction + Java code implementation)

Case introduction

The game character has attack power and defense power. It saves its own status (attack power and defense power) before fighting the boss. When the attack power and defense power decrease after fighting the boss, it can be restored from the memo object to the state before the battle.

traditional design

For each role, design a class to store the status of the role

【analyze】

  • An object corresponds to an object that saves the state of the object. In this way, when there are many objects in our game, it is not conducive to management and the cost is high.
  • The traditional way is to simply make a backup, create another object, and then put the data that needs to be backed up into this new object, but this exposes the internal details of the object.
  • Optimization method: use memo mode

introduce

basic introduction

  • The memento pattern captures the internal state of an object and saves this state outside the object without destroying encapsulation. This way you can later restore the object to its original saved state.
  • The memo pattern can be understood this way: memos in real life are used to record certain things to be done or to record common opinions that have been reached to prevent forgetting. At the software level, the memo object is mainly used to record a certain state of an object or certain data. When rolling back, the original data can be obtained from the memo object for recovery operations.
  • Memo mode is a behavioral mode

Characters

Insert image description here

  • Originator(生成者): The Originator character will generate the Memento character when saving its latest state. When passing a previously saved Memento character to the Originator character, it will restore itself to the state it was in when the Memento character was generated.
  • Memento(备忘录): The Memento role will integrate the internal information of the Originator role. Although the information of the Originator role is saved in the Memento role, it does not expose this information to the outside (by setting the permission characters of the method)
  • Caretaker(负责人): Notified when Caretaker角色you want to save the current state . An instance is generated and returned to after receiving the notification . Since it may be used to restore it to its original state later, it is always saved in the sample program. It can only use the narrow interface among the two interfaces (API), which means that it cannot access all the internal information (for example, in case three, only the money information can be obtained , and other operations cannot be performed). It just saves the generated one as a black box . Although there is a strong correlation between and , there is a weak correlation between and . Hiding one's own internal informationOriginator角色Originator角色Originator角色Memento角色Caretaker角色Memento实例OriginatorCaretaker角色Memento实例Caretaker角色Memento角色Memento角色Caretaker角色Memento角色创建Memento实例Originator角色Memento角色Originator角色Memento角色Caretaker角色Memento角色Memento角色Caretaker角色

[The meaning of division Caretaker角色and sum Originator角色]

The responsibility of the Caretaker role is to decide when to take a snapshot, when to undo and save the Memento role. On the other hand, the responsibility of the Originator role is to generate Memento roles and use the received Memento roles to restore its own state. With this kind of responsibility sharing, there is no need to modify the Originator role when we need to respond to the following demand changes:

  • Changes can be undone multiple times
  • Not only can changes be undone, but the current status can also be saved in a file.

[The Caretaker role can only operate the Memento role through a narrow interface (API). If the Caretaker role can freely operate the Memento role, what will happen?]

  • Destroying the encapsulation of the object: The Memento role is responsible for storing the status data of the Originator role. If the Caretaker role can operate the Memento role at will, then the encapsulation of the Memento object and the Originator object is destroyed.
  • Unsafe state recovery: If the Caretaker role can operate the Memento role at will, it may change the state data stored in the Memento object, causing errors in state recovery.
  • Data integrity cannot be guaranteed: If the Caretaker role can operate the Memento role at will, state data may be saved at incorrect time points, resulting in incomplete data

Case implementation

Case 1 (Basic Case)

Class Diagram

Insert image description here

  • Originator: Object that needs to be saved
  • Memento: Memo object, responsible for recording the status of the Originator object
  • Caretaker: Guardian object, responsible for saving multiple memo objects. Generally, collections are used for management to improve management efficiency.

accomplish

【Originator】

package com.atguigu.memento.theory;

public class Originator {
    
    

    /**
     * 角色的状态信息
     */
    private String state;

    public String getState() {
    
    
        return state;
    }

    public void setState(String state) {
    
    
        this.state = state;
    }

    /**
     * 编写一个方法,可以保存一个状态对象 Memento
     * 因此编写一个方法,返回 Memento
     *
     * @return
     */
    public Memento saveStateMemento() {
    
    
        return new Memento(state);
    }

    /**
     * 通过备忘录对象获取原有状态信息,恢复状态
     *
     * @param memento
     */
    public void getStateFromMemento(Memento memento) {
    
    
        state = memento.getState();
    }
}

【Memento】

package com.atguigu.memento.theory;

public class Memento {
    
    
    /**
     * 用来保存状态信息
     */
    private String state;

    /**
     * 构造器
     *
     * @param state
     */
    public Memento(String state) {
    
    
        super();
        this.state = state;
    }

    /**
     * 获取保存的状态信息
     * @return
     */
    public String getState() {
    
    
        return state;
    }

}

【Caretaker】

package com.atguigu.memento.theory;

import java.util.ArrayList;
import java.util.List;

/**
 * 统一管理备忘录对象
 */
public class Caretaker {
    
    

    /**
     * 在 List 集合中会有很多的备忘录对象
     * 如果想要保存多个Originator的多个状态,可以使用HashMap<Originator的ID,List<Memento>>
     */
    private List<Memento> mementoList = new ArrayList<Memento>();

    public void add(Memento memento) {
    
    
        mementoList.add(memento);
    }

    /**
     * 获取 Originator 的 第 index 个 备忘录对象(即所保存的状态)
     *
     * @param index
     * @return
     */
    public Memento get(int index) {
    
    
        return mementoList.get(index);
    }
}

【Main category】

package com.atguigu.memento.theory;

public class Client {
    
    

   public static void main(String[] args) {
    
    

      Originator originator = new Originator();
      // 备忘录对象管理器
      Caretaker caretaker = new Caretaker();

      originator.setState(" 状态#1 攻击力 100 ");

      //保存了当前的状态
      caretaker.add(originator.saveStateMemento());

      originator.setState(" 状态#2 攻击力 80 ");

      caretaker.add(originator.saveStateMemento());

      originator.setState(" 状态#3 攻击力 50 ");
      caretaker.add(originator.saveStateMemento());

      System.out.println("当前的状态是 =" + originator.getState());

      //希望得到状态 1, 将 originator 恢复到状态1
      originator.getStateFromMemento(caretaker.get(0));
      System.out.println("恢复到状态1 , 当前的状态是 =" + originator.getState());

   }

}

【run】

当前的状态是 = 状态#3 攻击力 50 
恢复到状态1 , 当前的状态是 = 状态#1 攻击力 100 

Process finished with exit code 0

Case 2

Class Diagram

Insert image description here

accomplish

【Originator:GameRole】

package com.atguigu.memento.game;

public class GameRole {
    
    

    private int vit;
    private int def;

    /**
     * 创建Memento,即根据当前的状态得到Memento
     *
     * @return
     */
    public Memento createMemento() {
    
    
        return new Memento(vit, def);
    }

    /**
     * 从备忘录对象,恢复GameRole的状态
     *
     * @param memento
     */
    public void recoverGameRoleFromMemento(Memento memento) {
    
    
        this.vit = memento.getVit();
        this.def = memento.getDef();
    }

    /**
     * 显示当前游戏角色的状态
     */
    public void display() {
    
    
        System.out.println("游戏角色当前的攻击力:" + this.vit + " 防御力: " + this.def);
    }

    public int getVit() {
    
    
        return vit;
    }

    public void setVit(int vit) {
    
    
        this.vit = vit;
    }

    public int getDef() {
    
    
        return def;
    }

    public void setDef(int def) {
    
    
        this.def = def;
    }


}

【Memento】

package com.atguigu.memento.game;

public class Memento {
    
    

    /**
     * 攻击力
     */
    private int vit;
    /**
     * 防御力
     */
    private int def;

    public Memento(int vit, int def) {
    
    
        this.vit = vit;
        this.def = def;
    }

    public int getVit() {
    
    
        return vit;
    }

    public void setVit(int vit) {
    
    
        this.vit = vit;
    }

    public int getDef() {
    
    
        return def;
    }

    public void setDef(int def) {
    
    
        this.def = def;
    }

}

【Caretaker】

package com.atguigu.memento.game;

/**
 * 守护者对象, 保存游戏角色的状态
 */
public class Caretaker {
    
    

    /**
     * 因为大战Boss之前只有一个状态,所以这里保存一次状态,不需要使用集合
     */
    private Memento memento;
    //对GameRole保存多次状态
    //private ArrayList<Memento> mementos;
    //对多个GameRole保存多个状态
    //private HashMap<String, ArrayList<Memento>> rolesMementos;

    public Memento getMemento() {
    
    
        return memento;
    }

    public void setMemento(Memento memento) {
    
    
        this.memento = memento;
    }

}

【Main category】

package com.atguigu.memento.game;

public class Client {
    
    

   public static void main(String[] args) {
    
    
      //创建游戏角色
      GameRole gameRole = new GameRole();
      gameRole.setVit(100);
      gameRole.setDef(100);

      System.out.println("和boss大战前的状态");
      gameRole.display();

      //把当前状态保存caretaker
      Caretaker caretaker = new Caretaker();
      caretaker.setMemento(gameRole.createMemento());

      System.out.println("和boss大战~~~");
      gameRole.setDef(30);
      gameRole.setVit(30);

      gameRole.display();

      System.out.println("大战后,使用备忘录对象恢复到站前");

      gameRole.recoverGameRoleFromMemento(caretaker.getMemento());
      System.out.println("恢复后的状态");
      gameRole.display();
   }

}

【run】

和boss大战前的状态
游戏角色当前的攻击力:100 防御力: 100
和boss大战~~~
游戏角色当前的攻击力:30 防御力: 30
大战后,使用备忘录对象恢复到站前
恢复后的状态
游戏角色当前的攻击力:100 防御力: 100

Process finished with exit code 0

Case three

Case description

  • The game is automated
  • The protagonist of the game decides the next state by rolling dice
  • When the dice number is 1, the protagonist's money will increase
  • When the dice number is 2, the protagonist's money will decrease
  • When the dice number is 6, the protagonist will get fruit
  • The game will end when the protagonist has no money

Class Diagram

Insert image description here

accomplish

【Originator:Gamer】

package com.atguigu.memento.Sample.game;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

public class Gamer {
    
    
    /**
     * 所持金钱
     */
    private int money;
    /**
     * 获得的水果
     */
    private List fruits = new ArrayList();
    /**
     * 随机数生成器
     */
    private Random random = new Random();
    /**
     * 表示水果种类的数组
     */
    private static String[] fruitsName = {
    
    
            "苹果", "葡萄", "香蕉", "橘子",
    };

    /**
     * 构造函数
     *
     * @param money
     */
    public Gamer(int money) {
    
    
        this.money = money;
    }

    /**
     * 获取当前所持金钱
     *
     * @return
     */
    public int getMoney() {
    
    
        return money;
    }

    /**
     * 投掷骰子进行游戏
     */
    public void bet() {
    
    
        // 掷骰子,随机获取一个点数
        int dice = random.nextInt(6) + 1;

        if (dice == 1) {
    
    
            //  骰子结果为1…增加所持金钱
            money += 100;
            System.out.println("所持金钱增加了。");
        } else if (dice == 2) {
    
    
            // 骰子结果为2…所持金钱减半
            money /= 2;
            System.out.println("所持金钱减半了。");
        } else if (dice == 6) {
    
    
            // 骰子结果为6…获得水果
            String f = getFruit();
            System.out.println("获得了水果(" + f + ")。");
            fruits.add(f);
        } else {
    
    
            // 骰子结果为3、4、5则什么都不会发生
            System.out.println("什么都没有发生。");
        }
    }

    /**
     * 拍摄快照
     *
     * @return
     */
    public Memento createMemento() {
    
    
        Memento m = new Memento(money);
        Iterator it = fruits.iterator();
        while (it.hasNext()) {
    
    
            String f = (String) it.next();
            if (f.startsWith("好吃的")) {
    
    
                // 只保存好吃的水果
                m.addFruit(f);
            }
        }
        return m;
    }

    /**
     * 撤销到指定状态
     *
     * @param memento
     */
    public void restoreMemento(Memento memento) {
    
    
        this.money = memento.money;
        this.fruits = memento.getFruits();
    }

    /**
     * 用字符串输出主人公的状态
     *
     * @return
     */
    public String toString() {
    
    
        return "[money = " + money + ", fruits = " + fruits + "]";
    }

    /**
     * 随机获取一个水果
     *
     * @return
     */
    private String getFruit() {
    
    
        String prefix = "";
        if (random.nextBoolean()) {
    
    
            prefix = "好吃的";
        }
        return prefix + fruitsName[random.nextInt(fruitsName.length)];
    }
}

【Memento】

package com.atguigu.memento.Sample.game;

import java.util.ArrayList;
import java.util.List;

public class Memento {
    
    
    /**
     * 游戏主人公所持金钱
     */
    int money;
    /**
     * 游戏主人公当前获得的水果
     */
    ArrayList fruits;
    /**
     * 获取当前所持金钱(narrow interface)
     * narrow interface:Memento角色为外部的 Caretaker 角色提供了“窄接口(API)”。可以通过窄接口(API)获取的Memento角色的内部信息非常有限,因此可以有效地防止信息泄露
     * @return
     */
    public int getMoney() {
    
    
        return money;
    }

    /**
     * 构造函数(wide interface),只有相同包的Gamer类才使用该构造方法,因为方法修饰类型是default
     *
     * @param money
     */
    Memento(int money) {
    
    
        this.money = money;
        this.fruits = new ArrayList();
    }

    /**
     * 添加水果(wide interface)
     *
     * @param fruit
     */
    void addFruit(String fruit) {
    
    
        fruits.add(fruit);
    }

    /**
     * 获取当前所持所有水果(wide interface)
     * wide interface:“宽接口(API)”是指所有用于获取恢复对象状态信息的方法的集合。由于宽接口(API)会暴露所有Memento角色的内部信息,因此能够使用宽接口(API)的只有Originator 角色
     * @return
     */
    List getFruits() {
    
    
        return (List) fruits.clone();
    }
}

【Caretaker】

package com.atguigu.memento.Sample;


import com.atguigu.memento.Sample.game.Gamer;
import com.atguigu.memento.Sample.game.Memento;

public class Main {
    
    
    public static void main(String[] args) {
    
    
        // 最初的所持金钱数为100
        Gamer gamer = new Gamer(100);
        // 保存最初的状态
        Memento memento = gamer.createMemento();
        for (int i = 0; i < 10; i++) {
    
    
            // 显示掷骰子的次数
            System.out.println("==== " + i);
            // 显示主人公现在的状态
            System.out.println("当前状态:" + gamer);

            // 进行游戏
            gamer.bet();

            System.out.println("所持金钱为" + gamer.getMoney() + "元。");

            // 决定如何处理Memento
            if (gamer.getMoney() > memento.getMoney()) {
    
    
                System.out.println("    (所持金钱增加了许多,因此保存游戏当前的状态)");
                memento = gamer.createMemento();
            } else if (gamer.getMoney() < memento.getMoney() / 2) {
    
    
                System.out.println("    (所持金钱减少了许多,因此将游戏恢复至以前的状态)");
                gamer.restoreMemento(memento);
            }

            // 等待一段时间
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
            }
            System.out.println("");
        }
    }
}

【run】

==== 0
当前状态:[money = 100, fruits = []]
什么都没有发生。
所持金钱为100元。

==== 1
当前状态:[money = 100, fruits = []]
所持金钱增加了。
所持金钱为200元。
    (所持金钱增加了许多,因此保存游戏当前的状态)

==== 2
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。

==== 3
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。

==== 4
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。

==== 5
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。

==== 6
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。

==== 7
当前状态:[money = 200, fruits = []]
什么都没有发生。
所持金钱为200元。

==== 8
当前状态:[money = 200, fruits = []]
所持金钱减半了。
所持金钱为100元。

==== 9
当前状态:[money = 100, fruits = []]
所持金钱增加了。
所持金钱为200元。


Process finished with exit code 0

expand

Use the Serialization function to save instances of the Memento class as files. Please modify the sample program to implement the following functions.

  • When the application starts, if it is found that game.datthe file does not exist, the game will start with the amount of money held as 100; if it is found that game.dat already exists, the game will start with the state saved in the file.
  • When the amount of money held increases significantly, save an instance of the Memento class to game.data file
  1. Inherit Memento from the Serializable interface to implement serialization operations. Other than that, no other modifications are required.
public class Memento implements Serializable {
    
    }
  1. If you want to extend the function of saving instances to files, you only need to modify Caretakerthe class
package com.atguigu.memento.A4;


import com.atguigu.memento.A4.game.Gamer;
import com.atguigu.memento.A4.game.Memento;

import java.io.*;

public class Main {
    
    
    /**
     * 数据保存的文件名
     */
    public static final String SAVE_FILE_NAME = "game.dat";

    public static void main(String[] args) {
    
    
        // 最初的所持金钱数为100
        Gamer gamer = new Gamer(100);
        // 从文件中读取起始状态
        Memento memento = loadMemento();
        if (memento != null) {
    
    
            System.out.println("读取上次保存存档开始游戏。");
            gamer.restoreMemento(memento);
        } else {
    
    
            System.out.println("新游戏。");
            memento = gamer.createMemento();
        }
        for (int i = 0; i < 100; i++) {
    
    
            // 显示次数
            System.out.println("==== " + i);
            // 显示当前主人公的状态
            System.out.println("当前状态:" + gamer);
            // 进行游戏
            gamer.bet();   

            System.out.println("所持金钱为" + gamer.getMoney() + "元。");

            // 决定如何处理Memento
            if (gamer.getMoney() > memento.getMoney()) {
    
    
                System.out.println("    (所持金钱增加了许多,因此保存游戏当前的状态)");
                memento = gamer.createMemento();
                // 将实例保存至文件中
                saveMemento(memento);
            } else if (gamer.getMoney() < memento.getMoney() / 2) {
    
    
                System.out.println("    (所持金钱减少了许多,因此将游戏恢复至以前的状态)");
                gamer.restoreMemento(memento);
            }

            // 等待一段时间
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
            }
            System.out.println("");
        }
    }

    /**
     * 保存实例到文件中
     * @param memento
     */
    public static void saveMemento(Memento memento) {
    
       
        try {
    
    
            ObjectOutput out = new ObjectOutputStream(new FileOutputStream(SAVE_FILE_NAME));
            out.writeObject(memento);
            out.close();
        } catch (IOException e) {
    
    
            e.printStackTrace();
        }
    }

    /**
     * 从文件中读取实例
     * @return
     */
    public static Memento loadMemento() {
    
                   
        Memento memento = null;
        try {
    
    
            ObjectInput in = new ObjectInputStream(new FileInputStream(SAVE_FILE_NAME));
            memento = (Memento)in.readObject();
            in.close();
        } catch (FileNotFoundException e) {
    
    
            System.out.println(e.toString());
        } catch (IOException e) {
    
    
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
    
    
            e.printStackTrace();
        }
        return memento;
    }
}

Summarize

【advantage】

  • Provides users with a mechanism to restore state, allowing users to more easily return to a historical state
  • Encapsulation of information is implemented so that users do not need to care about the details of saving the state.

【shortcoming】

  • If a class has too many member variables, it will inevitably occupy relatively large resources, and each save will consume a certain amount of memory. This needs to be noted. In order to save memory, memo mode can be used in conjunction with prototype mode.

[Applicable application scenarios]

  • Save files while playing games
  • in Windowsctrl+z
  • Back in IE
  • Database transaction management

Article description

  • This article is my study notes for studying Shang Silicon Valley. Most of the content in the article comes from Shang Silicon Valley videos ( click to learn Shang Silicon Valley related courses ), and some of the content comes from my own thinking. I publish this article to help other people who study more conveniently. Organize your own notes or learn relevant knowledge directly through articles. If there is any infringement, please contact us to delete it. Finally, I would like to express my gratitude to Shang Silicon Valley for its high-quality courses.
  • I also simultaneously read the book "Graphic Design Pattern" (Graphic Design Pattern/(Japanese) Hiroshi Yuki; translated by Yang Wenxuan - Beijing: People's Posts and Telecommunications Press, 2017.1), and then integrated the contents of the two to make the knowledge points more comprehensive.

Guess you like

Origin blog.csdn.net/laodanqiu/article/details/132189714