第六章 6.8 备忘录模式

分类:01_设计模式

标签:

6.8.1 备忘录模式介绍

备忘录模式提供了一种对象状态的撤销实现机制,当系统中某一个对象需要恢复到某一历史状态时可以使用备忘录模式进行设计.

123.jpg

很多软件都提供了撤销(Undo)操作,如 Word、记事本、Photoshop、IDEA等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 浏览器 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。

备忘录模式(memento pattern)定义: 在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态.

6.8.2 备忘录模式原理

125.jpg

备忘录模式的主要角色如下:

6.8.3 备忘录模式实现

下面我们再来看看 UML 对应的代码实现。首先,我们创建原始对象 Originator,对象中有四个属性,分别是 state 用于显示当前对象状态,id、name、phone 用来模拟业务属性,并添加 get、set 方法、create() 方法用于创建备份对象,restore(memento) 用于恢复对象状态。


/**
* 发起人类
* @author spikeCong
* @date 2022/10/19
**/
public class Originator {

   private String state = "原始对象";
   private String id;
   private String name;
   private String phone;

   public Originator() {
   }

   //创建备忘录对象
   public Memento create(){
       return new Memento(id,name,phone);
   }

   //恢复对象状态
   public void restore(Memento m){
       this.state = m.getState();
       this.id = m.getId();
       this.name = m.getName();
       this.phone = m.getPhone();
   }

   public String getState() {
       return state;
   }

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

   public String getId() {
       return id;
   }

   public void setId(String id) {
       this.id = id;
   }

   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
   }

   public String getPhone() {
       return phone;
   }

   public void setPhone(String phone) {
       this.phone = phone;
   }

   @Override
   public String toString() {
       return "Originator{" +
               "state='" + state + '\'' +
               ", id='" + id + '\'' +
               ", name='" + name + '\'' +
               ", phone='" + phone + '\'' +
               '}';
   }
}

/**
* 备忘录对象
*     访问权限为: 默认,也就是同包下可见(保证只有发起者类可以访问备忘录类)
* @author spikeCong
* @date 2022/10/19
**/
class Memento {

   private String state = "从备份对象恢复为原始对象";
   private String id;
   private String name;
   private String phone;

   public Memento() {
   }

   public Memento(String id, String name, String phone) {
       this.id = id;
       this.name = name;
       this.phone = phone;
   }

//get、set、toString......
}

/**
* 负责人类-保存备忘录对象
* @author spikeCong
* @date 2022/10/19
**/
public class Caretaker {

   private Memento memento;

   public Memento getMemento() {
       return memento;
   }

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

public class Client {

   public static void main(String[] args) {
       //创建发起人对象
       Originator originator = new Originator();
       originator.setId("1");
       originator.setName("spike");
       originator.setPhone("13512341234");
       System.out.println("=============" + originator);

       //创建负责人对象,并保存备忘录对象
       Caretaker caretaker = new Caretaker();
       caretaker.setMemento(originator.create());

       //修改
       originator.setName("update");
       System.out.println("=============" + originator);

       //从负责人对象中获取备忘录对象,实现撤销
       originator.restore(caretaker.getMemento());
       System.out.println("=============" + originator);
   }
}

6.8.4 备忘录模式应用实例

设计一个收集水果和获取金钱数的掷骰子游戏,游戏规则如下

  1. 游戏玩家通过扔骰子来决定下一个状态

  2. 当点数为1,玩家金钱增加

  3. 当点数为2,玩家金钱减少

  4. 当点数为6,玩家会得到水果

  5. 当钱消耗到一定程度,就恢复到初始状态


/**
* Memento 表示状态
* @author spikeCong
* @date 2022/10/19
**/
public class Memento {

   int money;    //所持金钱
   ArrayList fruits; //获得的水果

   //构造函数
   Memento(int money) {
       this.money = money;
       this.fruits = new ArrayList();
   }

   //获取当前玩家所有的金钱
   int getMoney() {
       return money;
   }

   //获取当前玩家所有的水果
   List getFruits() {
       return (List)fruits.clone();
   }

   //添加水果
   void addFruit(String fruit){
       fruits.add(fruit);
   }
}

package com.mashibing.memento.example02;

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

/**
* @author spikeCong
* @date 2022/10/19
**/
public class Player {

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

   //构造方法
   public Player(int money) {
       this.money = money;
   }

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

   //获取一个水果
   public String getFruit() {
       String prefix = "";
       if (random.nextBoolean()) {
           prefix = "好吃的";
       }

       //从数组中获取水果
       String f = fruitsName[random.nextInt(fruitsName.length)];
       return prefix + f;
   }

   //掷骰子游戏
   public void yacht(){

       int dice = random.nextInt(6) + 1;   //掷骰子
       if(dice == 1){
           money += 100;
           System.out.println("所持有的金钱增加了..");
       }else if(dice == 2){
           money /= 2;
           System.out.println("所持有的金钱减半..");
       }else if(dice == 6){   //获取水果
           String fruit = getFruit();
           System.out.println("获得了水果: " + fruit);
           fruits.add(fruit);
       }else{
           //骰子结果为3、4、5
           System.out.println("无效数字,继续投掷");
       }
   }

   //拍摄快照
   public Memento createMemento(){
       Memento memento = new Memento(money);
       for (String fruit : fruits) {
           if(fruit.startsWith("好吃的")){
               memento.addFruit(fruit);
           }
       }

       return memento;
   }

   //撤销方法
   public void restore(Memento memento){
       this.money = memento.money;
       this.fruits = memento.getFruits();
   }

   @Override
   public String toString() {
       return "Player{" +
               "money=" + money +
               ", fruits=" + fruits +
               '}';
   }
}

public class MainApp {

   public static void main(String[] args) throws InterruptedException {

       Player player = new Player(100);        //最初所持的金钱数
       Memento memento = player.createMemento();       //保存最初状态

       for (int i = 0; i < 100; i++) {
           //显示扔骰子的次数
           System.out.println("=====" + i);

           //显示当前状态
           System.out.println("当前状态: " + player);

           //开启游戏
           player.yacht();
           System.out.println("所持有的金钱为: " + player.getMoney() + " 元");

           //决定如何操作Memento
           if(player.getMoney() > memento.getMoney()){
               System.out.println("赚到金币,保存当前状态,继续游戏!");
               memento = player.createMemento();
           }else if(player.getMoney() < memento.getMoney() / 2){
               System.out.println("所持金币不多了,将游戏恢复到初始状态!");
               player.restore(memento);
           }

           Thread.sleep(1000);
           System.out.println("");
       }

   }
}


6.8.5 备忘录模式总结

1 ) 备忘录模式的优点

  1. 提供了一种状态恢复的实现机制,使得用户可以方便的回到一个特定的历史步骤,当新的状态无效或者存在问题的时候,可以使用暂时存储起来的备忘录将状态复原.

  2. 备忘录实现了对信息的封装,一个备忘录对象是一种发起者对象状态的表示,不会被其他代码所改动.备忘录保存了发起者的状态,采用集合来存储备忘录可以实现多次撤销的操作

2 ) 备忘录模式的缺点

3) 备忘录模式使用场景

  1. 需要保存一个对象在某一时刻的状态时,可以使用备忘录模式.

  2. 不希望外界直接访问对象内部状态时.


修改内容