备忘录模式

196 阅读3分钟

背景

我们在使用文本编辑器时,如果不小心删除了某一句话,可以通过撤销功能将文件恢复至之前的状态。而 Memento 是这样的一种设计模式,他事先将某个时间点的实例保存下来,之后有必要时,再将实例恢复至当时的状态。

Memento 实现的功能

  1. Undo(撤销)
  2. Redo(重做)
  3. History(历史记录)
  4. Snapshot(快照)

登场角色

Originator 生成者

Originator 角色会在保存自己的最新状态时生成 Memento 角色;把以前保存的 Memento 角色传递给 Originator 角色时,他将恢复到生成至该 Memeto 角色时的状态

Memento 纪念品

Memento 角色会将 Originator 角色的内部信息整合在一起,对于Memento 里面的信息,有两种获取的方法:

  • wide interface —— 宽接口

宽接口:用于获取恢复对象状态信息的方法集合,会暴露 Memento 对象里面所有信息,使用宽接口的仅限于 Originator 角色

  • narrow interface —— 窄接口

窄接口:为外部的Caretaker 角色 提供了窄接口,获取到的信息会十分有限,防止信息泄露

宽接口和窄接口有效地保护了对象的封装性,实现宽接口与窄接口其实依靠的是Java语言的可见性,宽接口使用 默认修饰符,窄接口使用 public 修饰

Caretaker 负责人

当 Caretaker 角色想要保存当前 Orignator 角色的状态时,会通知 Orignator 角色, Orignator 角色接收通知后会返回 Memento 角色。Caretaker角色会一直保存Memento实例,便于以后通过 Orignator 角色恢复实例

Caretaker 角色只能使用Memento 中的窄接口,它只是把 Originator 角色生成的Memento 角色 当做成一个黑盒

类图

示例代码

示例代码实现的功能是Game随机投骰子,如果是 金钱增加,就保存当前的实例,如果是 金钱减少且减少到一定程度,就会恢复原来保存的实例。

Gamer

public class Gamer {

    private int money;
    private ArrayList<String> fruits;
    private Random random = new Random();
    private String[] fruitsName = {"西瓜", "苹果", "雪梨"};

    public Gamer(int money) {
        this.money = money;
        this.fruits = new ArrayList<>();
    }

    public void bet() {
        int dict = random.nextInt(6) + 1;
        if (dict == 1) {
            money += 100;
            System.out.println("所持金钱增加了");
        } else if (dict == 2) {
            money /= 2;
            System.out.println("所持金钱减半了");
        } else if (dict == 6) {
            String f = getFruits();
            System.out.println("获得了水果(" + f + ").");
        } else {
            System.out.println("什么都没有发生");
        }
    }

    // 拍摄快照
    public Memento createMemento() {
        Memento memento = new Memento(money);
        Iterator<String> it = fruits.iterator();

        while (it.hasNext()) {
            String s = it.next();
            if (s.startsWith("好吃的")) {
                memento.addFruit(s);
            }
        }

        return memento;
    }

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

    public int getMoney() {
        return money;
    }

    private String getFruits() {
        String prefix = "";
        if (random.nextBoolean()) {
            prefix += "好吃的";
        }
        return prefix + fruitsName[random.nextInt(fruitsName.length)];
    }

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

Memento

public class Memento {

    int money;
    ArrayList<String> fruits;

    Memento(int money) {
        this.money = money;
        this.fruits = new ArrayList<>();
    }

    int getMoney() {
        return money;
    }

    void addFruit(String fruitName) {
        fruits.add(fruitName);
    }


    ArrayList<String> getFruits() {
        return (ArrayList<String>) fruits.clone();
    }

}

Main

public class Main {
    public static void main(String[] args) {
        Gamer gamer = new Gamer(100);
        Memento 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() + "元");
            if (gamer.getMoney() > memento.getMoney()) {
                memento = gamer.createMemento();
                System.out.println("所持金钱增加了,保存当前状态");
            } else if (gamer.getMoney() < memento.getMoney() / 2 ) {
                gamer.restoreMemento(memento);
                System.out.println("所持金钱减少了,恢复到游戏前的状态");
            }

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

功能分析

  1. 接口的可见性
可见性说明
Public所有类可以访问
Protected同一个包和子类可以访问
同一个包可以访问
Private只有类自身可以访问
  1. 实例代码中只保存了一个Memento ,可以使用一个数组或者集合来保存多个 Memento