二十一.行为型设计模式——Memento Pattern(备忘录模式)
-
定义:
在不破坏封装的前提下,捕获并且保存一个对象的内部状态,这样可以将对象恢复到原先保存的状态。
UML类图如下:
其中类和对象的关系为:
1.Memento(备忘录):保持Originator(原发器)的内部状态,根据原发器来决定保存哪些内部的状态;保护原发器之处的对象访问备忘录,备忘录可以有效地利用两个接口,看管者只能调用狭窄(功能有限)的接口——它只能传递备忘录给其他对象,而原发器可以调用一个宽阔(功能强大)的接口,通过这个接口可以访问所有需要的数据,使原发器可以返回先前的状态,理想的情况是,只允许生成本备忘录的那个原发器访问本备忘录的内部状态。
2.Originator(原发器):创建一个备忘录,记录它的当前内部状态;可以利用一个备忘录来恢复它的内部状态。
3.Caretaker(看管者):只负责看管备忘录;不可以对备忘录的内容操作或检查。
Memento模式经常在很多应用软件中出现,按Ctrl-Z会取消最后一次用户的操作,如果不用Memento模式,Craetaker对象要备份Originator对象的状态,Originator就要具有所以需要访问成员的方法,当要恢复Originator对象的状态时,Caretaker更要清楚Originator内部的结构。这种严紧的凝结的结果是,发生在Originator上的任何修改,Caretaker都要作出相应的修改,这样就打破了对象封装的特点。下图是没有采用Memento模式的结构:
Memento模式就是解决这种问题的最好方法,Memento的类成为了Originator及Caretaker的媒介,封装保存Originator的备份状态,当Originator被提出备份请求,它就会创建一个Memento对象返回给Caretaker。Caretaker不可以看到Memento对象的内部信息,需要时,Caretaker可以返回备份的Memento对象给Originator,让它恢复到备份状态,结构如图:
显然,Originator与Memento的关系是非常特殊的,它们要分享信息而不让其他类知道。实现的方法因编程语言的不同而不同,C++可以用friend关键字,Java及C#就需要将两个类放在一个包(域)内。
典型应用的顺序图如下:
-
实例1——销售目标:
以下的实例演示了Memento(备忘录)模式暂存并恢复SalesProspect对象的内部状态。UML类图如下:
//原发器——销售前景
class SalesProspect
{
private string name;
private string phone;
private double budget;
public string Name
{
get { return name; }
set { name = value; }
}
public string Phone
{
get { return phone; }
set { phone = value; }
}
//预算
public double Budget
{
get { return budget; }
set { budget = value; }
}
//保存备忘录
public Memento SaveMemento()
{
return new Memento(name, phone, budget);
}
//恢复备忘录
public void RestoreMemento(Memento memento)
{
this.name = memento.Name;
this.phone = memento.Phone;
this.budget = memento.Budget;
}
//显示信息
public void Show()
{
Console.WriteLine("\n销售期望------- ");
Console.WriteLine("名字:{0}", this.name);
Console.WriteLine("电话:{0}", this.phone);
Console.WriteLine("预算:{0:C}", this.budget);
}
}
//备忘录类
class Memento
{
private string name;
private string phone;
private double budget;
//构造函数初始化数据
public Memento(string name, string phone, double budget)
{
this.name = name;
this.phone = phone;
this.budget = budget;
}
public string Name
{
get { return name; }
set { name = value; }
}
public string Phone
{
get { return phone; }
set { phone = value; }
}
//预算
public double Budget
{
get { return budget; }
set { budget = value; }
}
}
//看管者
class ProspectMemory
{
private Memento memento;
//备忘录属性
public Memento Memento
{
get { return memento; }
set { memento = value; }
}
}
//备忘录应用测试
class Program
{
static void Main(string[] args)
{
SalesProspect s = new SalesProspect();
s.Name = "张主任";
s.Phone = "(020) 12560990";
s.Budget = 25000.0;
s.Show();
//保存内部状态
ProspectMemory m = new ProspectMemory();
m.Memento = s.SaveMemento();
//继续改变原发器
s.Name = "陈主任";
s.Phone = "(020) 12097111";
s.Budget = 100000.0;
s.Show();
//恢复原来保存的状态
s.RestoreMemento(m.Memento);
s.Show();
Console.Read();
}
}
-
优势和缺陷:
Memento模式保存了封装的边界,一个Memento对象是另一种原发器对象的表示,不会被其他代码改动。这种模式简化了原发器对象,Memento只保存原发器的状态。采用堆栈备忘对象,可以实现多次取消操作。
-
应用情景:
下面的情景很适合应用备忘录模式:
1.对象状态的备忘足以使对象可以完全恢复到原来的状态。
2.使用一个直接的接口来取得状态会使实现细节过程化,这样会打破对象的封装性。