意图
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。
结构
以及它们之间的协作关系:
动机
记录一个对象的内部状态。允许用户在必要的时候,通过恢复其状态来取消不确定的操作或从错误中恢复过来。
备忘录模式用一个备忘录(Memento)对象存储原发器(Originator)对象在某个瞬间的内部状态。在具体实现上,尽量(有些语言不支持)做到只有原发器可以向备忘录中存取信息,备忘录对其他对象 “不可见”。
适用性
- 必须保存一个对象在某一个时刻的状态(或部分状态), 这样以后需要时它才能恢复到先前的状态;
- 如果通过接口让其它对象直接得到这些私密的状态,又会暴露对象的实现细节并破坏对象的封装性;
注意事项
- 使用备忘录可以避免暴露那些只应由原发器管理却又必须存储在原发器之外的信息;
- 原先,原发器需保留所有请求的内部状态版本。现在,只需保留当前请求的内部状态版本,简化了原发器的设计;
- 如何保证只有原发器才能访问备忘录的状态,避免信息变相泄露;
- 能否通过增量式(存储)解决备忘录的各种开销,如存储开销、复制开销等。
示例
模拟一个图形编辑器,它使用MoveCommand命令对象来执行(或回退)一个Graphic图形对象从一个位置到另一个位置的变换。
实现(C#)
using System;
using System.Collections.Generic;
// 位置
public struct Point
{
public int X { get; set; }
public int Y { get; set; }
public override string ToString()
{
return string.Format("({0},{1})", this.X, this.Y);
}
public static Point operator -(Point obj)
{
return new Point{ X = -obj.X, Y = -obj.Y };
}
public static Point operator -(Point obj1, Point obj2)
{
return new Point{ X = obj1.X - obj2.X, Y = obj1.Y - obj2.Y };
}
public static bool operator ==(Point obj1, Point obj2)
{
if(obj1 != null && obj2 != null)
{
return obj1.Equals(obj2);
}
return false;
}
public static bool operator !=(Point obj1, Point obj2)
{
if(obj1 != null && obj2 != null)
{
return !obj1.Equals(obj2);
}
return false;
}
public override bool Equals(object obj)
{
if(obj is Point)
{
Point obj2 = (Point)obj;
return this.X == obj2.X && this.Y == obj2.Y;
}
return false;
}
public override int GetHashCode()
{
return this.X.GetHashCode() ^ this.Y.GetHashCode();
}
}
// 图块类
public class Graphic
{
// 图块名称
public string Name { get; private set;}
// 当前位置
public Point Position {get; private set;}
public Graphic(string name, Point position)
{
this.Name = name;
this.Position = position;
}
public void Move(Point delta)
{
bool flag = delta.X < 0 || delta.Y < 0;
delta.X += Position.X;
delta.Y += Position.Y;
Console.WriteLine("{0} 从 {1} {3}到 {2}", this.Name, this.Position, delta, (flag ? "撤销" : "移动"));
Position = delta;
ConstraintSolverMemento.ConstraintSolver solver = ConstraintSolverMemento.ConstraintSolver.GetInstance();
solver.Update(this);
}
public static bool operator ==(Graphic obj1, Graphic obj2)
{
if(obj1 != null && obj2 != null)
{
return obj1.Equals(obj2);
}
return false;
}
public static bool operator !=(Graphic obj1, Graphic obj2)
{
if(obj1 != null && obj2 != null)
{
return !obj1.Equals(obj2);
}
return false;
}
public override bool Equals(object obj)
{
if(obj != null && obj is Graphic)
{
Graphic obj2 = (obj as Graphic) ;
return this.Name == obj2.Name && this.Position == obj2.Position;
}
return false;
}
public override string ToString()
{
return string.Format("{0}{1}", this.Name, this.Position);
}
public override int GetHashCode()
{
return this.Name.GetHashCode() ^ this.Position.GetHashCode();
}
public Graphic Clone()
{
return new Graphic(this.Name, this.Position);
}
}
// 客户端移动命令,负责在外部保存备忘录信息。
public sealed class MoveCommand
{
private ConstraintSolverMemento memento;
private Point delta;
private Graphic target;
public MoveCommand(Graphic target, Point delta)
{
this.target = target;
this.delta = delta;
}
public void Execute()
{
ConstraintSolverMemento.ConstraintSolver solver = ConstraintSolverMemento.ConstraintSolver.GetInstance();
this.memento = solver.CreateMemento();
this.target.Move(delta);
solver.Solve();
}
public void Unexecute()
{
ConstraintSolverMemento.ConstraintSolver solver = ConstraintSolverMemento.ConstraintSolver.GetInstance();
target.Move(-this.delta);
solver.SetMemento(this.memento);
solver.Solve();
}
}
// 备忘录类
public class ConstraintSolverMemento
{
// 只允许原发器(ConstraintSolver)访问备忘录(ConstraintSolverMemento)的内部信息。
private List<Tuple<Graphic,Graphic>> Paths { get; set;}
// 原发器类(图块之间的线路关系)
public class ConstraintSolver
{
private static ConstraintSolver instance;
private List<Tuple<Graphic,Graphic>> Paths { get; set;}
// 单例模式(不考虑线程安全)。
public static ConstraintSolver GetInstance()
{
if (instance == null)
{
instance = new ConstraintSolver();
}
return instance;
}
private ConstraintSolver() {}
public void Update(Graphic graphic)
{
Tuple<Graphic,Graphic> find = null;
foreach(Tuple<Graphic,Graphic> item in Paths)
{
if(item.Item1.Name == graphic.Name || item.Item2.Name == graphic.Name)
{
find = item;
break;
}
}
if(find != null)
{
this.Paths.Remove(find);
if(find.Item1.Name == graphic.Name)
{
this.Paths.Add(new Tuple<Graphic,Graphic>(graphic.Clone(), find.Item2.Clone()));
}
else
this.Paths.Add(new Tuple<Graphic,Graphic>(find.Item1.Clone(), graphic.Clone()));
}
}
public void AddConstraint(Graphic start, Graphic end)
{
if(this.Paths == null) this.Paths = new List<Tuple<Graphic,Graphic>> ();
foreach(Tuple<Graphic,Graphic> item in Paths)
{
if(item.Item1 == start && item.Item2 == end) return;
}
Paths.Add(new Tuple<Graphic,Graphic>(start.Clone(),end.Clone()));
}
public void RemoveConstraint(Graphic start, Graphic end)
{
foreach(Tuple<Graphic,Graphic> item in Paths)
{
if(item.Item1 == start && item.Item2 == end) Paths.Remove(item);
}
}
public ConstraintSolverMemento CreateMemento()
{
return new ConstraintSolverMemento { Paths = this.Paths };
}
public void SetMemento(ConstraintSolverMemento memento)
{
this.Paths = memento.Paths;
}
// 绘画图块之间的线路
public void Solve()
{
foreach(Tuple<Graphic,Graphic> item in this.Paths)
{
Console.WriteLine(" 路线打印:{0} 到 {1} 有一条连接线.", item.Item1, item.Item2);
}
Console.WriteLine();
}
}
}
// 测试。
public class App
{
public static void Main(string[] args)
{
Graphic g1 = new Graphic("A", new Point { X = 0, Y = 0});
Graphic g2 = new Graphic("B", new Point { X = 20, Y = 0});
MoveCommand command1 = new MoveCommand(g1, new Point { X = 15, Y = 0 });
MoveCommand command2 = new MoveCommand(g2, new Point { X = 45, Y = 0 });
ConstraintSolverMemento.ConstraintSolver solver = ConstraintSolverMemento.ConstraintSolver.GetInstance();
solver.AddConstraint(g1,g2);
Console.WriteLine("初始位置:图块{0},图块{1}\n", g1,g2);
command1.Execute();
command2.Execute();
command2.Unexecute();
command1.Unexecute();
}
}
// 控制台输出:
// 初始位置:图块A(0,0),图块B(20,0)
// A 从 (0,0) 移动到 (15,0)
// 路线打印:A(15,0) 到 B(20,0) 有一条连接线.
// B 从 (20,0) 移动到 (65,0)
// 路线打印:A(15,0) 到 B(65,0) 有一条连接线.
// B 从 (65,0) 撤销到 (20,0)
// 路线打印:A(15,0) 到 B(20,0) 有一条连接线.
// A 从 (15,0) 撤销到 (0,0)
// 路线打印:A(0,0) 到 B(20,0) 有一条连接线.