备忘录模式
1、备忘录模式介绍
备忘录模式是一种行为型设计模式,用于在不破坏封装性的前提下保存和恢复对象的状态。该模式通常包括三个角色:发起者(Originator)、备忘录(Memento)和管理者(Caretaker)。
1.1 存在问题
如果在工作中不使用备忘录模式,可能会导致一些问题。比如,当我们需要保存程序或对象的某个状态以便在将来某个时刻恢复时,如果我们没有合适的机制来管理状态,那么就很难实现这样的需求。此时,我们可能需要手动记录状态并将其存储到文件或数据库中,同时还需要管理和维护状态的版本控制,这会很耗费时间和精力。而且,如果我们的程序需要支持多种状态的保存和恢复,那么这个过程会变得更加困难。
因此,在这种情况下,使用备忘录模式可以很好地解决这个问题。它提供了一个简单而有效的方法来捕获和存储对象的内部状态,从而使我们可以在需要时轻松地恢复对象的状态。同时,备忘录模式也有助于保持封装性,因为对象的状态仅限于它自己和备忘录对象之间共享,其他对象无法访问或更改状态。
例如
假设你正在为公司编写一个重要的软件程序,这个程序中包含了一些复杂的业务逻辑。你需要测试和修改这个程序,但是由于代码比较复杂,你很难在第一次测试时分辨出哪些代码有问题,哪些代码没有问题。如果此时你不使用备忘录模式来保存程序的某个状态以便在将来某个时刻回溯到之前的状态,那么你每次都需要重新验证每一段代码的正确性,这会非常浪费时间和精力,并且有可能漏掉一些问题。
另外,如果你在修改程序时不小心破坏了某些核心代码,而此时你没有任何备份或历史记录,那么整个程序就可能遭受损失,这也是没有使用备忘录模式会存在的问题。而如果你使用备忘录模式,就可以轻松地恢复到之前的某个状态,避免了这种风险。
再例如
假设一个学生正在上课,突然需要离开教室一段时间,但他不希望错过老师所讲解的内容。如果他没有任何方法来记录老师的讲解,那么他在回来之后就无法恢复自己的知识状态,可能会影响以后的学习。但是,如果他能够使用备忘录模式来记录老师的讲解,就可以轻松地恢复到离开教室前的知识状态。
1.2 备忘录模式
备忘录(Memento):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
Originator(发起人):负责创建一个备忘录Memento,用以记录当 前时刻它的内部状态,并可使用备忘录恢复内部状态。Originator 可根据需要决定Memento存储Originator的哪些内部状态。
Memento(备忘录):负责存储Originator对象的内部状态,并可防 止Originator以外的其他对象访问备忘录Memento。备忘录有两个接 口,Caretaker只能看到备忘录的窄接口,它只能将备忘录传递给其 他对象。Originator能够看到一个宽接口,允许它访问返回到先前 状态所需的所有数据。
Caretaker(管理者):负责保存好备忘录Memento,不能对备忘录 的内容进行操作或检查。
1.3 备忘录模式基本代码
备忘录类:
/** * @author Shier * CreateTime 2023/5/10 11:31 * 备忘录类 */ public class Memento { private String state; public Memento(String state) { this.state = state; } public String getState() { return state; } public void setState(String state) { this.state = state; } }
发起人(初始)类:
/** * @author Shier * CreateTime 2023/5/10 11:26 * 发起人(Originator)类 */ public class Originator { /** * 状态 */ private String state; /** * 显示数据 */ public void show() { System.out.println("Current State : " + this.state); } /** * 创建备忘录 * * @return 将当前需要保存的信息导入并实例化出一个Memento对象 */ public Memento createMemento(){ return new Memento(this.state); } /** * 恢复备忘录 */ public void recoveryMemento(Memento memento){ this.setState(memento.getState()); } /** * 需要保存的属性 * * @return */ public String getState() { return state; } public void setState(String state) { this.state = state; } }
管理类:
/** * @author Shier * CreateTime 2023/5/10 11:35 * 管理类 */ public class Caretaker { private Memento memento; public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento = memento; } }
客户端类:
/** * @author Shier * CreateTime 2023/5/10 11:36 */ public class MementoClient { public static void main(String[] args) { // 原始类初始化 Originator originator = new Originator(); originator.setState("ON"); originator.show(); // 保存当前状态 Caretaker caretaker = new Caretaker(); caretaker.setMemento(originator.createMemento()); // 状态进行改变 originator.setState("OFF"); originator.show(); // 恢复初始的状态 originator.recoveryMemento(caretaker.getMemento()); originator.show(); } }
输出结果:
2、具体例子说明
例子:游戏的某个场景,一游戏角色有生命力、攻击力、防御力等数据,在打Boss前和后的数据一定会不一样的,我们允许玩家如果感觉与Boss决斗的效果不理想可以让游戏恢复到战斗前。
初始状态:
战斗后:
2.1 不使用备忘录模式
游戏角色:
/** * @author Shier * CreateTime 2023/5/10 11:45 * 游戏角色类 */ public class GameRole { /** * 生命力 */ private int vitality; /** * 攻击力 */ private int attack; /** * 防御力 */ private int defense; /** * 状态显示 */ public void displayState() { System.out.println("角色当前状态:"); System.out.println("生命力:" + this.vitality); System.out.println("攻击力:" + this.attack); System.out.println("防御力:" + this.defense); System.out.println(); } /** * 初始状态 * * @return */ public void getInitState() { this.vitality = 100; this.attack = 100; this.defense = 100; } /** * 战斗后状态 * * @param */ public void fight() { this.vitality = 0; this.attack = 0; this.defense = 0; } public int getVitality() { return vitality; } public void setVitality(int vitality) { this.vitality = vitality; } public int getAttack() { return attack; } public void setAttack(int attack) { this.attack = attack; } public int getDefense() { return defense; } public void setDefense(int defense) { this.defense = defense; } }
客户端:
/** * @author Shier * CreateTime 2023/5/10 11:51 */ public class GameClient { public static void main(String[] args) { // 战斗前 GameRole gameRole = new GameRole(); gameRole.getInitState(); gameRole.displayState(); // 保存进度 GameRole backRole = new GameRole(); backRole.setAttack(gameRole.getAttack()); backRole.setDefense(gameRole.getDefense()); backRole.setVitality(gameRole.getVitality()); // 战斗 gameRole.fight(); gameRole.displayState(); // 恢复战斗前状态 gameRole.setVitality(backRole.getVitality()); gameRole.setDefense(backRole.getDefense()); gameRole.setAttack(backRole.getAttack()); // 显示恢复后的状态 gameRole.displayState(); } }
输出结果:
分析
确实实现了例子的要求,问题主要在于这客户端的调用。下面的程序存在问题,因为这样写就把整个游戏角色的细节暴露给了客户端,客户端的职责就太大了,需要知道游戏角色的生命力、攻击力、防御力这些细节,还要对它进行’备份’。以后需要增加新的数据,例如增加 ‘魔法力’ 或修改现有的某种力,例如 ‘生命力’ 改为 ‘经验值’,这部分就一定要修改了。同样的道理也存在于恢复时的代码。
解决:
显然,我们希望的是把这些’游戏角色’的存取状态细节封装起来,而且 最好是封装在外部的类当中。以体现职责分离。
2.2 使用备忘录模式实现
代码结构图:
角色状态记录类:
/** * @author Shier * CreateTime 2023/5/10 11:59 * 角色状态记录 */ public class RoleStateMemento { /** * 生命力 */ private int vitality; /** * 攻击力 */ private int attack; /** * 防御力 */ private int defense; public RoleStateMemento(int vitality, int attack, int defense) { this.vitality = vitality; this.attack = attack; this.defense = defense; } public int getVitality() { return vitality; } public void setVitality(int vitality) { this.vitality = vitality; } public int getAttack() { return attack; } public void setAttack(int attack) { this.attack = attack; } public int getDefense() { return defense; } public void setDefense(int defense) { this.defense = defense; } }
角色类:
/** * @author Shier * CreateTime 2023/5/10 11:45 * 游戏角色类 */ public class GameRole { /** * 生命力 */ private int vitality; /** * 攻击力 */ private int attack; /** * 防御力 */ private int defense; /** * 状态显示 */ public void displayState() { System.out.println("角色当前状态:"); System.out.println("生命力:" + this.vitality); System.out.println("攻击力:" + this.attack); System.out.println("防御力:" + this.defense); System.out.println(); } /** * 初始状态 * * @return */ public void getInitState() { this.vitality = 100; this.attack = 100; this.defense = 100; } /** * 战斗后状态 * * @param */ public void fight() { this.vitality = 0; this.attack = 0; this.defense = 0; } /** * 保存角色状态 * * @return */ public RoleStateMemento savaState(){ return new RoleStateMemento(this.vitality,this.attack,this.defense); } /** * 恢复角色状态 * @return */ public void recoveryState(RoleStateMemento roleStateMemento){ this.setAttack(roleStateMemento.getAttack()); this.setDefense(roleStateMemento.getDefense()); this.setVitality(roleStateMemento.getVitality()); } public int getVitality() { return vitality; } public void setVitality(int vitality) { this.vitality = vitality; } public int getAttack() { return attack; } public void setAttack(int attack) { this.attack = attack; } public int getDefense() { return defense; } public void setDefense(int defense) { this.defense = defense; } }
角色管理类:
/** * @author Shier * CreateTime 2023/5/10 12:04 * 角色管理类 */ public class RoleStateCaretaker { private RoleStateMemento roleStateMemento; public RoleStateMemento getRoleStateMemento() { return roleStateMemento; } public void setRoleStateMemento(RoleStateMemento roleStateMemento) { this.roleStateMemento = roleStateMemento; } }
客户端类:
/** * @author Shier * CreateTime 2023/5/10 11:51 */ public class GameClient2 { public static void main(String[] args) { // 战斗前 GameRole role = new GameRole(); role.getInitState(); role.displayState(); // 保存进度 RoleStateCaretaker caretaker = new RoleStateCaretaker(); caretaker.setRoleStateMemento(role.savaState()); // 战斗 role.fight(); role.displayState(); // 恢复战斗前状态 role.recoveryState(caretaker.getRoleStateMemento()); // 显示恢复后的状态 role.displayState(); } }
输出结果同上
- 把要保存的细节给封装在了RoleStateMemento中了, 哪一天要更改保存的细节也不用影响客户端了。
- 使用备忘录可以把复杂的对象内部信息对其他的对象屏蔽起来
- 在当角色的状态改变的时候,有可能这个状态无效,这时候就可以使用暂时存储起来的备忘录将状态复原
3、总结
备忘录模式优点:
可以在不破坏对象封装性的前提下,捕获和恢复对象的内部状态;
可以对状态进行存储和恢复,保证程序的稳定性;
可以缩小“黑盒”对象和“白盒”对象之间的耦合度,提高程序的可扩展性和可维护性;
可以有效地管理多个历史状态,方便用户选择并恢复到特定的状态。
备忘录模式缺点:
对象状态的保存和恢复会消耗大量的内存,特别是针对大型对象的情况;
如果状态的保存和恢复涉及到较多的属性(或数据),则备忘录对象可能会非常庞大,导致程序效率低下。
备忘录模式使用场景:
需要保存和恢复对象状态的情况;
需要提供撤销操作的情况;
需要记录对象历史状态的情况;
需要动态地保存和恢复对象状态的情况;
功能比较复杂的,但需要维护或记录属性历史的类,或者需要保存的属性只是众多属性中的一小部分时,Originator可以根据保存的Memento信息还原到前一状态。
to中了, 哪一天要更改保存的细节也不用影响客户端了。
使用备忘录可以把复杂的对象内部信息对其他的对象屏蔽起来
在当角色的状态改变的时候,有可能这个状态无效,这时候就可以使用暂时存储起来的备忘录将状态复原