游戏角色状态恢复问题
游戏角色有攻击力和防御力,在大战 Boss 前保存自 身的状态攻击力和防御力当大战 Boss 后攻击力和防御力下降, 从备忘录对象恢复到大战前的状态
传统方案
对象 <-> 对象状态
对象 <-> 对象状态
对象 <-> 对象状态
传统方式的问题分析
1.一 个对象,就对应一个保存对象状态的对象, 这样当我们游戏的对象很多时,不利于管理,开销也很大
2.传统的方式是简单地做备份(new 出另外一个对象出来),再把需要备份的数据放到这个新对象,但这就暴露了对象内部的细节
备忘录模式
备忘录模式( Memento Pattern) 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态
可以这里理解备忘录模式:现实生活中的备忘录是用来记录某些要去做的事情,或者是记录已经达成的共同意见的事情,以防忘记了。而在软件层面,备忘录模式有着相同的含义,备忘录对象主要用来记录一个对象的某种状态,或者某些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作
备忘录模式结构
1.原发器 (Originator)
类可以生成自身状态的快照, 也可以在需要时通过快照恢复自身状态。
2.备忘录 (Memento)
是原发器状态快照的值对象 (value object)。 通常做法是将备忘录设为不可变的, 并通过构造函数一次性传递数据。
3.负责人(Caretaker)
仅知道 “何时” 和 “为何” 捕捉原发器的状态, 以及何时恢复状态。
负责人通过保存备忘录栈来记录原发器的历史状态。 当原发器需要回溯历史状态时, 负责人将从栈中获取最顶部的备忘录, 并将其传递给原发器的恢复 (restoration) 方法。
在该实现方法中, 备忘录类将被嵌套在原发器中。 这样原发器就可访问备忘录的成员变量和方法, 即使这些方法被声明为私有。 另一方面, 负责人对于备忘录的成员变量和方法的访问权限非常有限: 它们只能在栈中保存备忘录, 而不能修改其状态。
备忘录模式适合应用场景
1.当你需要创建对象状态快照来恢复其之前的状态时, 可以使用备忘录模式。
2.当直接访问对象的成员变量、 获取器或设置器将导致封装被突破时, 可以使用该模式。
实现方式
-
确定担任原发器角色的类。 重要的是明确程序使用的一个原发器中心对象, 还是多个较小的对象。
-
创建备忘录类。 逐一声明对应每个原发器成员变量的备忘录成员变量。
-
将备忘录类设为不可变。 备忘录只能通过构造函数一次性接收数据。 该类中不能包含设置器。
-
如果你所使用的编程语言支持嵌套类, 则可将备忘录嵌套在原发器中; 如果不支持, 那么你可从备忘录类中抽取一个空接口, 然后让其他所有对象通过接口来引用备忘录。 你可在该接口中添加一些元数据操作, 但不能暴露原发器的状态。
-
在原发器中添加一个创建备忘录的方法。 原发器必须通过备忘录构造函数的一个或多个实际参数来将自身状态传递给备忘录。
该方法返回结果的类型必须是你在上一步中抽取的接口 (如果你已经抽取了)。 实际上, 创建备忘录的方法必须直接与备忘录类进行交互。
-
在原发器类中添加一个用于恢复自身状态的方法。 该方法接受备忘录对象作为参数。 如果你在之前的步骤中抽取了接口, 那么可将接口作为参数的类型。 在这种情况下, 你需要将输入对象强制转换为备忘录, 因为原发器需要拥有对该对象的完全访问权限。
-
无论负责人是命令对象、 历史记录或其他完全不同的东西, 它都必须要知道何时向原发器请求新的备忘录、 如何存储备忘录以及何时使用特定备忘录来对原发器进行恢复。
-
负责人与原发器之间的连接可以移动到备忘录类中。 在本例中, 每个备忘录都必须与创建自己的原发器相连接。 恢复方法也可以移动到备忘录类中, 但只有当备忘录类嵌套在原发器中, 或者原发器类提供了足够多的设置器并可对其状态进行重写时, 这种方式才能实现。
备忘录模式优缺点
优点:
✔️ 你可以在不破坏对象封装情况的前提下创建对象状态快照。
✔️ 你可以通过让负责人维护原发器状态历史记录来简化原发器代码。
缺点:
❌ 如果客户端过于频繁地创建备忘录, 程序将消耗大量内存。
❌ 负责人必须完整跟踪原发器的生命周期, 这样才能销毁弃用的备忘录。
❌ 绝大部分动态编程语言 (例如 PHP、 Python 和 JavaScript) 不能确保备忘录中的状态不被修改。
解决游戏角色状态恢复问题
代码:
public class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
public class GameRole {
private String state; // 状态信息
public String getState(){return state;}
public void setState(String state){this.state=state;}
//保存状态对象Memento
public Memento saveState(){
return new Memento(state);
}
//恢复状态
public void getStateFromMemento(Memento memento){
state = memento.getState();
}
}
public class Caretaker {
//存放备忘录对象
private List<Memento> mementoList = new ArrayList<>();
public void add(Memento memento){
mementoList.add(memento);
}
//获取 所需下标 的 备忘录对象
public Memento get(int index){
return mementoList.get(index);
}
}
public class Client {
public static void main(String[] args) {
GameRole gameRole = new GameRole();
Caretaker caretaker = new Caretaker();
gameRole.setState("状态1:攻击力100,防御力50");
//保存状态1
caretaker.add(gameRole.saveState());
gameRole.setState("状态2:攻击力120,防御力60");
//保存状态2
caretaker.add(gameRole.saveState());
gameRole.setState("状态3:攻击力150,防御力70");
//保存状态3
caretaker.add(gameRole.saveState());
System.out.println("当前的状态为"+gameRole.getState());
System.out.println("恢复状态1");
gameRole.getStateFromMemento(caretaker.get(0));
System.out.println("-->"+gameRole.getState());
System.out.println("恢复状态3");
gameRole.getStateFromMemento(caretaker.get(2));
System.out.println("-->"+gameRole.getState());
}
}
备忘录模式的注意事项和细节
1.给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态
2.实现了信息的封装,使得用户不需要关心状态的保存细 节
3.如果类的成员变量过多,势必会占用比较大的资源 ,而且每一次保存都会消耗一定的内存 [需要注意]
4.适用的应用场景: 1)后悔药。 2)打游戏时的存档。 3 )Windows 里的 ctrl+ z 。4 ) IE 中的后退。 5)数据库的事务管理
5.为了节约内存 ,备忘录模式可以和原型模式配合使用