1、對象狀態的回溯
對象狀態的變化無故,如何回溯/恢復對象在某個點的狀態?
2、動機(Motivation)
1)在軟件構建過程當中,某些對象的狀態在轉換過程當中,可能因爲某種須要,要求程序可以回溯到對象以前處於某個點時的狀態。若是使用一些公有接口來讓其餘對象獲得對象的狀態,便會暴露對象的細節實現。
2)如何實現對象狀態的良好保存與恢復?但同時又不會所以而破壞對象自己的封裝性。
3、意圖(Intent)
在不破壞封裝性的前提下,捕獲一個對象的內部狀態,並在該對象以外保存這個狀態。這樣之後就能夠將該對象恢復到原先保存的狀態。
——《設計模式》GoF
4、實例:保存矩形的狀態
1)很原始的作法,它可以解決這個問題,但破壞封裝性
//矩形
public class Rectangle : ICloneable
{
private int x; //X座標
private int y; //Y座標
private int width; //寬
private int height; //高
public Rectangle(int x, int y, int width, int height)
{
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
public void SetValue(Rectangle r)
{
this.x = r.x;
this.y = r.y;
this.width = r.width;
this.height = r.height;
}
//克隆
public object Clone()
{
return this.MemberwiseClone();
}
//移動矩形
public void MoveTo(Point p)
{
}
//改變寬
public void ChangWidth(int width)
{
}
//改變高
public ChangeHeight(int height)
{
}
//繪製
public void Draw(Graphic graphic)
{
}
}數據庫
//操做圖形
public class GraphicsSystem
{
//原發器對象
//有必要對自身狀態進行保存,而後在某個點處又須要恢復的對象
Rectangle r = = new Rectangle(0, 0, 10, 10);
//備忘錄對象,用來保存原發器對象的狀態,
//可是不提供原發器對象支持的操做
Rectangle rSaved = = new Rectangle(0, 0, 10, 10);
//rSaved如今有擁有改變他本身屬性的方法,因此它也可能被改變
public void Process(Rectangle r)
{
rSaved = r.Clone();
//...
}
public void Saved_Click(object sender, EventArgs e)
{
r.SetValue(rSaved);
}
}設計模式
2)演變成備忘錄Memento模式:不破壞對象的封裝性,這就是備忘錄模式要解決的問題
//矩形
public class Rectangle
{
private int x; //X座標
private int y; //Y座標
private int width; //寬
private int height; //高
public Rectangle(int x, int y, int width, int height)
{
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
//移動矩形
public void MoveTo(Point p)
{
}
//改變寬
public void ChangWidth(int width)
{
}
//改變高
public ChangeHeight(int height)
{
}
//繪製
public void Draw(Graphic graphic)
{
}
//保存
public RectangMemento CreateMemento()
{
RectangleMemento rm = new RectangleMemento();
rm.SetState(this.x, this.y, this.width, this.height);
return rm;
}
//恢復
public void SetMemento(RectangleMemento rm)
{
this.x = rm.x;
this.y = rm.y;
this.width = rm.width;
this.height = rm.height;
}
}this
//新建了這個類,用來保存矩形的狀態,只封裝狀態,不提供其餘操做
public class RectangleMemento
{
internal int x; //X座標
internal int y; //Y座標
internal int width; //寬
internal int height; //高
internal void SetState(int x, int y, int width, int height)
{
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
}線程
//操做圖形:GraphicsSystem處於另外的程序集中
public class GraphicsSystem
{
//原發器對象
//有必要對自身狀態進行保存,而後在某個點處又須要恢復的對象
Rectangle r = = new Rectangle(0, 0, 10, 10);
//備忘錄對象,用來保存原發器對象的狀態,
//可是不提供原發器對象支持的操做
RectangleMemento rSaved = new RectangleMemento();
public void Process(Rectangle r)
{
rSaved = r.CreateMemento();
//...
}
public void Saved_Click(object sender, EventArgs e)
{
r.SetMemento(rSaved);
}
}設計
3)序列化的方式保存狀態
[Serializable]
public class Rectangle
{
private int x; //X座標
private int y; //Y座標
private int width; //寬
private int height; //高
public Rectangle(int x, int y, int width, int height)
{
this.x = x;
this.y = y;
this.width = width;
this.height = height;
}
//移動矩形
public void MoveTo(Point p)
{
}
//改變寬
public void ChangWidth(int width)
{
}
//改變高
public ChangeHeight(int height)
{
}
//繪製
public void Draw(Graphic graphic)
{
}
public void CreateMemento()
{
GeneralMementor gm = new GeneralMementor();
gm.SetState(this);
}
public void SetMemento(GeneralMementor memento)
{
Rectangle r = (Rectangle)memento.GetSatte();
this.x = r.x;
this.y = r.y;
this.width = r.width;
this.height = r.height;
}
}orm
//通用備忘錄:適合任何支持序列化的對象
//使用序列化和工廠方法模式配合能夠保存在內存中,線程內,線程外,數據庫等
public class GeneralMementor
{
private Stream rSaved;
public GeneralMemento(Factory factory)
{
rSaved = factory.CreateStream();
}
internal void SetState(object obj)
{
//序列化
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(obj, rSaved);
}
internal object GetState()
{
//反序列化
BinaryFormatter bf = new BinaryFormatter();
rSaved.Seek(Seek.Original, 0);//將流的遊標移到開頭
object obj = (Rectangle)bf.DeSerialize(rSaved);
return obj;
}
}對象
5、Memento模式的幾個要點
1)備忘錄(Memento)存儲原發器(Originator)對象的內部狀態,在須要時恢復原發器狀態。Memento模式適用於「由原發器管理,卻又必須存儲在原發器以外的信息」。
2)在實現Memento模式中,要防止原發器之外的對象訪問備忘錄對象。備忘錄對象有兩個接口,一個爲原發器使用的寬接口;一個爲其餘對象使用的窄接口。
3)在實現Memento模式時,要考慮拷貝對象狀態的效率問題,若是對象開銷比較大,能夠採用某種增量式改變來改進Memento模式。接口