把书读薄 | 《设计模式之美》设计模式与范式(行为型-备忘录模式)

1,180 阅读3分钟

这是我参与8月更文挑战的第7天,活动详情查看: 8月更文挑战

0x0、引言

🤣 今天周四!开机混底薪多一天就放假啦!继续啃设计模式,本文对应设计模式与范式:行为型(70),备忘录模式 (Memento Pattern),又称 快照模式,主要用于 防丢失撤销恢复 等,模式很好理解,代码实现比较灵活~

Tips:二手知识加工难免有所纰漏,感兴趣有时间的可自行查阅原文,谢谢。


0x1、定义

原始定义

不违背封装原则 的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,一遍之后 恢复 对象为先前的状态。

很简单,直接上例子~

0x2、写个简单例子

RPG游戏的例子,存档保存当前血量、蓝量、拥有的金币,支持读档~

// 备忘录
public class Memento {
    private final int hp;
    private final int mp;
    private final int money;

    public Memento(int hp, int mp, int money) {
        this.hp = hp;
        this.mp = mp;
        this.money = money;
    }

    public int getHp() { return hp; }
    public int getMp() { return mp; }
    public int getMoney() { return money; }
}

// 发起人
public class Character {
    private int hp;
    private int mp;
    private int money;

    public Character(int hp, int mp, int money) {
        this.hp = hp;
        this.mp = mp;
        this.money = money;
    }

    public int getHp() { return hp; }
    public void setHp(int hp) { this.hp = hp; }
    public int getMp() { return mp; }
    public void setMp(int mp) { this.mp = mp; }
    public int getMoney() { return money; }
    public void setMoney(int money) { this.money = money; }

    @Override public String toString() {
        return "当前状态: HP: " + hp + " | MP: " + mp + " | 金钱: " + money;
    }

    // 创建备忘录,保存当前状态
    public Memento save() {
        return new Memento(hp, mp, money)
    }

    // 传入备忘录对象,恢复内部状态
    public void restore(Memento memento) {
        this.hp = memento.getHp();
        this.mp = memento.getHp();
        this.money = memento.getMoney();
    }
}

// 测试用例
public class MementoTest {
    public static void main(String[] args) {
        Memento memento;
        Character character = new Character(1000, 100, 500);
        // 存档
        System.out.println("存档中...");
        System.out.println(character);
        memento = character.save();
        System.out.println("单挑boss不敌,金钱扣除一半...");
        character.setHp(0);
        character.setMp(0);
        character.setMoney(character.getMoney() / 2);
        System.out.println(character);
        System.out.println("读取存档...");
        character.restore(memento);
        System.out.println(character);
    }
}

代码运行结果如下

上面这种是一个存档的情况,如果想多个存档,可以弄个管理者,负责备忘录对象的传递~

// 管理者
public class Caretaker {
    private Map<Long, Memento> mementoMap = new HashMap();

    public void getMementoList() {
        for(Long timeStamp: mementoMap.keySet()) {
            System.out.println("【" + timeStamp + "】" + mementoMap.get(timeStamp));
        }
    }

    public Memento getMementoByTimeStamp(Long timeStamp) {
        return mementoMap.get(timeStamp);
    }

    public Memento getMementoByPos(int pos) {
        if (pos < mementoMap.size()) {
            for(Map.Entry<Long, Memento> entry : mementoMap.entrySet()) {
                if(pos == 0) {
                    return entry.getValue();
                } else {
                    pos--;
                }
            }
        }
        return null;
    }

    public void createMemento(Long timeStamp, Memento memento) {
        mementoMap.put(timeStamp, memento);
    }
}

// 修改后的测试用例
public class MementoTest {
    public static void main(String[] args) throws InterruptedException {
        Caretaker caretaker = new Caretaker();
        Character character = new Character(1000, 100, 500);
        // 存档
        System.out.println("存档中...");
        Thread.sleep(10L);
        System.out.println(character);
        caretaker.createMemento(System.currentTimeMillis(), character.save());
        System.out.println("单挑boss不敌,金钱扣除一半...");
        character.setHp(0);
        character.setMp(0);
        character.setMoney(character.getMoney() / 2);
        System.out.println("存档中...");
        Thread.sleep(10L);
        System.out.println(character);
        caretaker.createMemento(System.currentTimeMillis(), character.save());
        System.out.println("\n重新打开游戏,查看可用存档:");
        caretaker.getMementoList();
        character.restore(caretaker.getMementoByPos(0));
        System.out.println("加载存档...");
        System.out.println(character);
    }
}

代码运行结果如下

非常简单,直接带出UML类图、组成角色、使用场景和优缺点~

  • Originator (发起者) → 处自身所需的属性和业务外,提供保存和恢复对象副本的方法;
  • Memento (备忘录) → 保存原始对象的所有属性状态;
  • Caretaker (管理者) → 备选,负责备忘录对象的传递,保存;

使用场景

  • 需要保存一个对象在某个时刻的状态,以便后续撤销恢复,而又不希望外界直接访问对象的内部状态;

优点

  • 较好的封装性,发起者对象的内部实现细节不会暴露给外部,且保证只有发起者才能访问备忘录对象的状态;
  • 较好的可扩展性,外部对象保存原始对象状态,而不是在原始对象中新增状态记录,不需要保存时取消方便;

缺点

  • 资源消耗问题,要备份的成员变量太多,不可避免需占用大量存储空间每保存依次对象状态都需要消耗一定的系统资源。对象数据很大时,除了备份耗时长外,还可能出现内存用尽导致系统崩溃的情况。

Tips优化备份的内存和时间消耗

低频率全量备份高频率增量备份,Git的版本管理就是一个很好的例子: