你又是怎么理解「Memento Pattern」的呢

962 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第33天,点击查看活动详情

备忘录模式

我们在使用文本编辑器编写文件时,如果不小心删除了某句话,可以通过撤销(undo )功能将文件恢复至之前的状态。有些文本编辑器甚至支持多次撤销,能够恢复至很久之前的版本。

使用面向对象编程的方式实现撤销功能时,需要事先保存实例的相关状态信息。然后,在撤销时,还需要根据所保存的信息将实例恢复至原来的状态。

事例程序

  • 游戏是自动进行的
  • 游戏的主人公通过掷骰子来决定下一个状态
  • 当骰子点数为1的时候,主人公的金钱会增加
  • 当骰子点数为2的时候,主人公的金钱会减少
  • 当骰子点数为6的时候,主人公会得到水果
  • 主人公没有钱时游戏就会结束

在程序中,如果金钱增加,为了方便将来恢复状态,我们会生成Memento类的实例,将现在的状态保存起来。所保存的数据为当前持有的金钱和水果。如果不断掷出了会导致金钱减少的点数,为了防止金钱变为0而结束游戏,我们会使用Memento的实例将游戏恢复至之前的状态。

类的一览表

名字说明
Mementogame的状态类
Gamer游戏的主人公
Main测试

类图

image-20221227201430515

代码

Memento

  • money表示主人公现在所持有的金钱数目,fruits表示现在为止所获得的水果。
public class Memento {
    int money;//所持金钱
    ArrayList<String> fruits;//所获得水果
​
    public int getMoney() {
        return money;
    }
​
    Memento(int money) {
        this.money = money;
        this.fruits = new ArrayList<>();
    }
//添加水果
    void addFruit(String fruit) {
        fruits.add(fruit);
    }
//获取所有水果
    List getFruit() {
        return (List) fruits.clone();
    }
}

Gamer

Gamer类是表示游戏主人公的类。它有3个子段,即所持金钱(money)获得的水果(fruits )以及一个随机数生成器(random)。而且还有一个名为fruitsname 的静态字段。

  • bet:,只要主人公没有破产,就会一直掷骰子,并根据骰子结果改变所持有的金钱数目和水果个数。
  • createMemento方法的作用是保存当前的状态(拍摄快照)。在createMemento方法中,会根据当前在时间点所持有的金钱和水果生成一个Memento类的实例,该实例代表了“当前Gamer的状态”,它会被返回给调用者。(只保存好吃的水果)
  • restoreMemento方法的功能与createMemento相反,它会根据接收到的Memento类的实例来将Gamer恢复为以前的状态
public class Gamer {
    private int money;
    private List<String> fruits = new ArrayList<>();
    private Random random = new Random();
    private static String[] fruitName = {"苹果", "葡萄", "香蕉", "橘子"};
​
    public Gamer(int money) {
        this.money = money;
    }
​
    public int getMoney() {
        return money;
    }
​
    public void bet() {
        //投骰子游戏
        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 f = getFruit();
            System.out.println("获得水果" + f);
            fruits.add(f);
        } else {
            //3,4,5无事发生
            System.out.println("什么都没有发生");
        }
    }
  //拍摄快照
    public Memento createMemento() {
        Memento m = new Memento(money);
        Iterator<String> it = fruits.iterator();
        while (it.hasNext()) {
            String f = it.next();
            //保存好吃的水果
            if (f.startsWith("好吃的")) {
                m.addFruit(f);
            }
        }
        return m;
    }
  //撤销
    public void restoreMemento(Memento memento) {
        this.money = memento.money;
        this.fruits = memento.getFruit();
    }
​
    @Override
    public String toString() {
        return "money = " + money + " fruits = " + fruits;
    }
  //获得水果
    private String getFruit() {
        String prefix = "";
        if (random.nextBoolean()) {
            prefix = "好吃的";
        }
        return prefix + fruitName[random.nextInt(fruitName.length)];
    }
}

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()) {
                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) {
                e.printStackTrace();
            }
        }
    }
}

时序图

image-20221227202246403