什么是备忘录模式?
老规矩,我们先来看看备忘录模式 (Memento) 的定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
它的UML类图如下:
从上图可以发现,备忘录模式其实整体分为三个部分:Originator(发起人)、备忘录(Memento)、CareTaker(管理者)。其中,Originator负责创建一个备忘录Memento,并哟个memento记录当前时刻它的内部状态
部分或整体) ,同时,也可以使用备忘录Memento恢复到某一个状态;备忘录Memento负责记录、存储发起人Originator的内部状态,并且只有发起人Originator能够访问其内部数据;而管理者Caretaker就负责保存备忘录Memento,注意,它不会查看备忘录里的具体内容。
举一个大家熟知的例子,SL大法知道吧?
在游戏里,当我们保存进度时,玩家角色就是一个发起人,而角色的当前状态(等级、血量、魔法值等)被保存(Save);当我们想重来的时候,读取存档(Load),就能够恢复角色到之前某一个时刻的状态。上述整个过程其实就是备忘录模式的具体体现,接下来我们就以游戏保存、读取来看看备忘录模式的具体应用。
如何使用备忘录模式?
先定义一个英雄类,表示玩家操作的游戏角色,他拥有生命值、魔法值、攻击力、防御力:
- 英雄类
@Data public class Hero { private int hp; private int mp; private int atk; private int def; // 初始角色状态 public void getInitialState() { this.hp = 100; this.mp = 50; this.atk = 10; this.def = 5; } // 当前状态展示 public void displayState() { System.out.println("角色当前状态:"); System.out.println("生命值:" + this.hp); System.out.println("魔法值:" + this.hp); System.out.println("攻击力:" + this.atk); System.out.println("防御力: " + this.def); System.out.println("******************"); } // 角色经验升级,恢复一定生命、魔法并增加属性 public void levelUp() { this.hp += 20; this.mp += 10; this.atk += 5; this.def += 5; } // 大战boss public void fight() { this.hp -= 99; this.mp -= 50; } // 保存现在状态 public SavaMemento saveState() { return (new SavaMemento(hp, mp, atk, def)); } // 恢复之前状态 public void loadState(SavaMemento memento) { this.hp = memento.getHp(); this.mp = memento.getMp(); this.atk = memento.getAtk(); this.atk = memento.getDef(); } }
我们注意到,英雄类里除了初始化、战斗等函数,有两个公共方法:SaveMemento和loadState,前者返回了一个存档,后者则根据这个存档恢复到某一个时刻的状态。其实也可以这么理解,保存存档的时候,得到了一个存档id,之后读取存档则需要该存档id。
- 存档类
存档类的实现很简单,就是根据最初系统的设计目的,来保存对应的英雄类中的某些信息。
public class SavaMemento { private int hp; // 生命力 private int mp; // 魔法值 private int atk; // 攻击力 private int def; // 防御力 public SavaMemento(int hp, int mp, int atk, int def) { this.hp = hp; this.mp = hp; this.atk = atk; this.def = def; } }
- 存档保管箱
存档保存角色信息,而存档保管箱则是将存档的实现细节收敛,防止客户端知道其具体的实现细节。
public class CareTaker { private SavaMemento savaLoadMemento; public SavaMemento loadGame() { try { if (savaLoadMemento != null) return savaLoadMemento; else throw new Exception(); } catch (Exception ignored) { System.out.println("The saveFile is missed!"); } return new SavaMemento(100, 50, 10, 5); } public void saveGame(SavaMemento memento) { this.savaLoadMemento = memento; } }
- 游戏客户端
设计一个游戏客户端,操作一个游戏角色从出生到大战boss、再到读取存档的过程:
public class GameClient { public static void main(String[] args) { // 显示初始状态 System.out.println("初始状态--"); Hero hero = new Hero(); hero.getInitialState(); hero.displayState(); // 保存游戏 System.out.println("保存游戏--"); CareTaker caretaker = new CareTaker(); caretaker.saveGame(hero.saveState()); // 打死大boss System.out.println("三拳打死Boss--"); hero.fight(); hero.displayState(); // 升级啦 System.out.println("经验升级--"); hero.levelUp(); hero.displayState(); // 恢复游戏 System.out.println("读取存档--"); hero.loadState(caretaker.loadGame()); hero.displayState(); } }
- 来看看输出
总结
应用场景
备忘录模式主要应用在这样的场景:需要记录某一个对象的内部状态,并且能够将对象恢复到某一个时刻状态,例如用户操作redo、或者游戏进度保存等等。
优点
提供了一种可以恢复状态的机制,进行回溯甚至将对象恢复到任意时刻的状态。
缺点
会额外消耗资源,因为需要将对象的状态信息进行保存,不过这个缺点在实际业务场景下都是可以接受的。
参考资料
《大话设计模式》