备忘录(Memento)模式,又称为令牌(Token)模式,它可以在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将对象恢复至原来的状态。
一般在以下情况下使用备忘录模式:
必须保存一个对象在某一时刻的(部分)状态,以保证需要时将它恢复。而如果用一个接口来让其他对象直接获得这些状态,会暴露对象的细节并破坏其封装性。
备忘录模式经常会用在文本、图像编辑工具里,用以支持Undo、Redo操作。
游戏中我们也可看到备忘录模式的身影,例如某个Moba类游戏中,某个英雄的某个技能就是回到过去。本文就以此为例,探究一下备忘录模式的实现。
备忘录类:
class HeroStateMemento
{
public int hp { get; private set;}
public int mp { get; private set;}
public HeroStateMemento(int hp_, int mp_)
{
hp = hp_;
mp = mp_;
}
}
使用构造函数将hp和mp保存起来。
英雄类:
class Hero
{
public int hp { get; private set;}
public int mp { get; private set;}
public HeroStateMemento SaveState()
{
return new HeroStateMemento (hp, mp);
}
public void RecoverState(HeroStateMemento memento)
{
hp = memento.hp;
mp = memento.mp;
}
public void Initialize()
{
hp = 100;//TEST
mp = 100;//TEST
}
public void BeDamaged(int dmg)
{
hp -= dmg;
}
public void CostMana(int mana)
{
mp -= mana;
}
}
由英雄来创建备忘录,并且可以根据备忘录里保存的hp和mp恢复自身的状态。在备忘录模式的结构了,我们把英雄称之为原发器(Originator)。
我们还缺少一个备忘录管理者,它负责保存备忘录,本文就实现了可以将备忘录顺序保存为一个列表的管理者:
class HeroStateCaretaker
{
private List<HeroStateMemento> _stateList = new List<HeroStateMemento>();
private int _stateMax;
public HeroStateCaretaker(int stateMax)
{
_stateMax = stateMax;
}
public void AddStateMemento(HeroStateMemento memento)
{
_stateList.Add (memento);
while (_stateList.Count > _stateMax) {
_stateList.RemoveAt (0);
}
}
public HeroStateMemento GetStateMemento(int idx)
{
idx = Math.Min (Math.Max (idx, 0), _stateMax - 1);
return _stateList [idx];
}
}
使用:
Hero anub = new Hero ();
anub.Initialize ();
HeroStateCaretaker caretaker = new HeroStateCaretaker (10);
caretaker.AddStateMemento (anub.SaveState ());
System.Random r = new System.Random ();
for (int i = 0; i < 10; i++) {
anub.BeDamaged (r.Next(0, 10));
anub.CostMana (r.Next(0, 10));
caretaker.AddStateMemento (anub.SaveState ());
}
Console.WriteLine (anub.hp);
Console.WriteLine (anub.mp);
anub.RecoverState (caretaker.GetStateMemento (0));
Console.WriteLine (anub.hp);
Console.WriteLine (anub.mp);
备忘录有以下优点:
1、保持封装边界,可以避免暴露一些应由原发器管理却又必须保存在原发器之外的信息。
2、简化了原发器,将存储管理任务和原发器的其他逻辑任务分离。
缺点:
1、生成备忘录的代价可能很高,因为生成备忘录必须复制大量信息,再或者用户可能会频繁的创建备忘录和恢复状态,这都会导致非常大的开销。
2、一些语言中无法保证只有原发器可以访问备忘录的状态。对于C++来说,可以讲原发器作为备忘录的友元,但是对于C#或者一些其他的语言,如果原发器需要修改备忘录的话,那么就需要公开备忘录的属性或者方法,这样就破坏了备忘录的封装性。