备忘录模式(Memento)在游戏中可用来做存储功能。在书中使用备忘录模式”存储成就记录”
直接将数据存盘功能实现于游戏功能类中,一般来说不太理想,因为违反了单一职责原则(SRP)。也就是说,各个游戏功能类应该专心处理与游戏相关的功能。
而记录保存,也应交由专职的记录保存类
为说明要专门写一个记录保存类呢?
书中给出了解释说明:
如果在功能模块中实现,会将内部成员数据全部公开。
虽然可以少写代码,缺违背了接口隔离原则(ISP)即除非必要,类应该尽量减少对外显示内部的数据接口,以减少客户端有机会破坏内部成员的记录,而对外公布过多的操作方法,容易增加与其他系统的耦合度。
GOF对备忘录模式的定义:
在不违反封装的原则下,获取一个对象的内部状态并保留在外部,让该对象可以在日后恢复到原先保留时的状态。
这里列出几个问题,来做思考。
如何在“不违反封装的原则” 还能提供内部的详细信息?
就是由游戏系统本身“主动提供内部信息”存盘功能,而且也“主动”向存盘功能提供自己“系统”有关的信息。
这与原本由游戏本身提供一大堆“存取内部成员”的方法,有什么不同?最大的不同在于:
游戏系统提供存取内部成员方法,是让游戏系统处于“被动”状态。
备忘录模式则是将游戏系统由“被动”改为“主动提供”,意思是,
游戏系统自己决定要提供什么信息和记录存盘功能,也由游戏系统决定要从存盘功能中,读取什么样的数据及记录还原给内部成员。
而这些信息记录的设置和获取的实现地点都在“游戏系统类内”,不会发生在游戏系统类以外的地方,如此就可确保类的“封装的原则”不会被破坏。
记录拥有者Originator
记录保存者Memento
管理记录保存者Caretaker
public class Originator{
string m_State; //状态,需要被保存
public void SetInfo(string State){
m_State = State;
}
public void ShowInfo(){
Debug.Log("Originator State"+m_State);
}
//产生要存储的记录
public Memento CreateMemento(){
Memeto newMemento = new Memento();
newMemento.SetState(m_State);
return newMemento;
}
//设置要恢复的记录
public void SetMemento(Memento m){
m_State = m.GetState();
}
}
Originator(记录拥有者)类内拥有一个需要被保存的成员:m_State,而这个成员将由Originator(记录拥有者)在CreateMemento方法中产生记录保存者对象,最后传出到客户端。
客户端可以将之前保留的记录保存者对象通过记录拥有者的类方法:SetMemento传入类中,让Originator可以恢复到之前记录的状态。
public class Memento{
string m_State;
public string GetState(){
return m_State;
}
public void SetState(string State){
m_State = State;
}
}
很简单的一个类,封装了数据,通过Set Get 方法来读取、存储数据
void UnitTest(){
Originator theOriginator = new Originator();
//设置信息
theOriginator.SetInfo("Step1");
theOriginator.ShowInfo();
//存储状态
Memento theMemento = theOriginator.CreateMemento();
//设置新的信息
theOriginator.SetInfo("Step2");
theOriginator.ShowInfo();
//恢复
theOriginator.SetMemento(theMemento);
theOriginator.ShowInfo();
}
//测试代码
在测试代码中,先将记录拥有者设置为Step1之后,利用CreateMemento方法将内部状态保留下来(theMemento)。
随后设置为“Step2”,但假设此时设置发生错误,只要将之前保留的状态,利用SetMemento方法再设置回去就可以了
public class Caretaker{
Dictionary<string,Memento> m_Mementos = new Dictionary<string,Memento>();
//增加
public void AddMemento(string Version,Memento theMemento){
if(m_Mementos.ContainsKey(Version) == false)
m_Menmentos.Add(Version,theMemento);
else{
m_Mementos[Version] = theMemento;
}
}
//读取
public Memento GetMemento(string Version){
if(m_Nementos.CointainsKey(Version)) == false){
return null;
}
return m_Mementos[Version];
}
}
void UnitTest(){
Originator theOriginator = new Originator();
CareteKer theCaretaker = new Caretaker();
//设置信息
theOriginator.SetInfo("Version1");
theOriginator.ShowInfo();
//保存
theCaretaker.AddMemento("1",theOriginator.CreateMemento());
//设置信息
theOriginator.SetInfo("Version2");
theOriginator.ShowInfo();
//保存
theCaretaker.AddMemento("2",theOriginator.CreateMemento());
//设置信息
theOriginator.SetInfo("Version3");
theOriginator.ShowInfo();
//保存
theCaretaker.AddMemento("3",theOriginator.CreateMemento());
///退回到第2版
theOriginator.SetMementor(theCaretaker.GetMemento("2"));
theOriginator.ShowInfo();
//退回到第1版
theOriginator.SetMemento(theCaretaker.GetMemento("1"));
theOriginator.ShowInfo();
}
第二个测试加入了记录对象管理类(Caretaker),记录拥有者(theOriginator)就转而向记录管理类找到自己需要的版本,通过过字典容器找到获取这个记录(Memento)
运用备忘录模式Memento 提供了一个不破坏原有类封装性的“对象状态保存”方案,并让对象状态保存可以存在多个版本,并且还可以选择要恢复到的哪个版本。
与其他模式的合作:
如果备忘录模式搭配命令模式来作为命令执行前的系统状态保存,就能让命令在执行恢复操作时,能够恢复到命令执行前的状态。
其他应用方式
日志系统用备忘录模式,让游戏系统产生的信息定期存储。此时日志系统就只负责存储记录。