意圖
在不破壞封裝性的前提下涂滴,捕獲一個(gè)對象的內(nèi)部狀態(tài),并在該對象之外保存這個(gè)狀態(tài)缔杉。這樣以后就可以將該對象恢復(fù)到原先保存的狀態(tài)搁料。
結(jié)構(gòu)
以及它們之間的協(xié)作關(guān)系:
動機(jī)
記錄一個(gè)對象的內(nèi)部狀態(tài)。允許用戶在必要的時(shí)候霸琴,通過恢復(fù)其狀態(tài)來取消不確定的操作或從錯誤中恢復(fù)過來昭伸。
備忘錄模式用一個(gè)備忘錄(Memento)對象存儲原發(fā)器(Originator)對象在某個(gè)瞬間的內(nèi)部狀態(tài)。在具體實(shí)現(xiàn)上庐杨,盡量(有些語言不支持)做到只有原發(fā)器可以向備忘錄中存取信息,備忘錄對其他對象 “不可見”仁堪。
適用性
- 必須保存一個(gè)對象在某一個(gè)時(shí)刻的狀態(tài)(或部分狀態(tài)), 這樣以后需要時(shí)它才能恢復(fù)到先前的狀態(tài)各吨;
- 如果通過接口讓其它對象直接得到這些私密的狀態(tài),又會暴露對象的實(shí)現(xiàn)細(xì)節(jié)并破壞對象的封裝性揭蜒;
注意事項(xiàng)
- 使用備忘錄可以避免暴露那些只應(yīng)由原發(fā)器管理卻又必須存儲在原發(fā)器之外的信息;
- 原先徙融,原發(fā)器需保留所有請求的內(nèi)部狀態(tài)版本。現(xiàn)在欺冀,只需保留當(dāng)前請求的內(nèi)部狀態(tài)版本萨脑,簡化了原發(fā)器的設(shè)計(jì);
- 如何保證只有原發(fā)器才能訪問備忘錄的狀態(tài)渤早,避免信息變相泄露;
- 能否通過增量式(存儲)解決備忘錄的各種開銷悴灵,如存儲開銷骂蓖、復(fù)制開銷等。
示例
模擬一個(gè)圖形編輯器登下,它使用MoveCommand命令對象來執(zhí)行(或回退)一個(gè)Graphic圖形對象從一個(gè)位置到另一個(gè)位置的變換。
實(shí)現(xiàn)(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;}
// 當(dāng)前位置
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);
}
}
// 客戶端移動命令,負(fù)責(zé)在外部保存?zhèn)渫浶畔ⅰ?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
{
// 只允許原發(fā)器(ConstraintSolver)訪問備忘錄(ConstraintSolverMemento)的內(nèi)部信息揩瞪。
private List<Tuple<Graphic,Graphic>> Paths { get; set;}
// 原發(fā)器類(圖塊之間的線路關(guān)系)
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) 有一條連接線.