在軟件構(gòu)建過程中,某些對象的狀態(tài)在轉(zhuǎn)換過程中,可能由于某種需要,要求程序能夠回溯到對象之前處于某個點時的狀態(tài).如果使用一些共有接口來讓其他對象得到對象的狀態(tài),便會暴露對象的細(xì)節(jié)實現(xiàn)。我們需要實現(xiàn)對象狀態(tài)的良好保存與恢復(fù),但同時不會因此而破壞對象本身的封裝性。
Examda提示:在不破壞封裝性的前提下,捕獲一個對象的內(nèi)部狀態(tài),并在該對象之外保存這個狀態(tài)。這樣以后就可以將該對象恢復(fù)到原先保存的狀態(tài)。
我們首先看看不適用設(shè)計模式來解決對象狀態(tài)恢復(fù)的情況。
public class Rectangle : ICloneable
{
int x;
int y;
int width;
int height;
public void SetValue(Rectangle r)
{
this.x = r.x;
this.y = r.y;
this.width = r.width;
this.height = r.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 ChangeWidth(int width)
{
}
public void ChangeHeight(int height)
{
}
public void Draw(Graphics graphic)
{
}
#region ICloneable 成員
public object Clone()
{
return this.MemberwiseClone();
}
#endregion
}
public class GraphicsSystem
{
//原發(fā)器對象:
//有必要對自身內(nèi)部狀態(tài)進(jìn)行保存,然后在某個點處又需要恢復(fù)內(nèi)部狀態(tài)的對象
Rectangle r = new Rectangle(0, 0, 10, 10);
//備忘錄對象:
//保存原發(fā)器對象的內(nèi)部狀態(tài),但不提供原發(fā)器對象支持的操作
Rectangle rSaved = new Rectangle(0, 0, 10, 10);
public void Process()
{
rSaved = r.Clone();
//....
}
public void Saved_Click(object sender, EventArgs e)
{
r.SetValue(rSaved);
//....
}
}
class Program
{
static void Main(string[] args)
{
Rectangle r = new Rectangle(0, 0, 10, 10);
GraphicsSystem g = new GraphicsSystem();
g.Process(r);
}
}
上面的代碼中Rectangle類實現(xiàn)了ICloneable接口,這個接口利用淺拷貝返回一個新的Rectangle對象
在GraphicsSystem類中,我們定義了一個原發(fā)器對象r,和備忘錄對象rSaved,在Process的時候,我們將原發(fā)器對象進(jìn)行克隆保存在rSaved引用中。在Saved_Click方法中,將備忘錄對象rSaved保存的值還原給原發(fā)器對象r。但這樣來做,備忘錄對象提過了原發(fā)器對象的一些操作,那么我們現(xiàn)在需要將備忘錄對象抽象出來。
public class Rectangle
{
int x;
int y;
int width;
int height;
public void SetValue(Rectangle r)
{
this.x = r.x;
this.y = r.y;
this.width = r.width;
this.height = r.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 ChangeWidth(int width)
{
}
public void ChangeHeight(int height)
{
}
public void Draw(Graphics graphic)
{
}
internal RectangleMemento Creatememento()
{
RectangleMemento memento = new RectangleMemento();
memento.SetState(this.x, this.y, this.width, this.height);
return memento;
}
internal void SetMemento(RectangleMemento memento)
{
this.x = memento.x;
this.y = memento.y;
this.width = memento.width;
this.height = memento.height;
}
}
internal class RectangleMemento
{
internal int x;
internal int 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;
}
}
public class GraphicsSystem
{
//原發(fā)器對象:
//有必要對自身內(nèi)部狀態(tài)進(jìn)行保存,然后在某個點處又需要恢復(fù)內(nèi)部狀態(tài)的對象
Rectangle r = new Rectangle(0, 0, 10, 10);
//備忘錄對象:
//保存原發(fā)器對象的內(nèi)部狀態(tài),但不提供原發(fā)器對象支持的操作
RectangleMemento rSaved = new RectangleMemento();
public void Process(Rectangle r)
{
rSaved = r.Creatememento();
//....
}
public void Saved_Click(object sender, EventArgs e)
{
r.SetMemento(rSaved);
//....
}
}
在上面這段代碼中,我們將備忘錄對象抽象出來為RectangleMemento類,這個類只保存了原發(fā)器對象基本的值,但沒有提供其他的操作,并且,我們將RectangleMemento類和內(nèi)部成員全部申明為internal只能讓程序集本身調(diào)用,保證了RectangleMemento對象的封裝性。
實現(xiàn)要點:
備忘錄存儲原發(fā)器(Originator)對象的內(nèi)部狀態(tài),在需要時恢復(fù)原發(fā)器狀態(tài)。Memento模式適用于由原發(fā)器管理,卻又必須存儲在原發(fā)器之外的信息
在實現(xiàn)Memento模式中,要防止原發(fā)器以外的對象方位備忘錄對象,備忘錄對象有兩個接口,一個為原發(fā)器使用的寬接口,一個為其他對象使用的窄接口
在實現(xiàn)Memento模式時,要考慮拷貝對象狀態(tài)的效率問題,如果對象開銷比較大,可以采用某種增量式改變來跟進(jìn)Memnto模式
在上面的例子中Rectangle對于RectangleMemento看到是寬接口,即SetState方法,而GraphicsSystem看到的是窄接口,即RectangleMemento的構(gòu)造函數(shù)和Creatememento、SetMemento方法?!‘?dāng)一個對象比較大的時候,在.net中如DataSet,要保存對象的狀態(tài)可能會造成效率問題,占用比較大的內(nèi)存。
下面一段代碼演示了通過使用序列化的方式來實現(xiàn)Memento模式
[Serializable]
public class Rectangle
{
int x;
int y;
int width;
int height;
public void SetValue(Rectangle r)
{
this.x = r.x;
this.y = r.y;
this.width = r.width;
this.height = r.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 ChangeWidth(int width)
{
}
public void ChangeHeight(int height)
{
}
public void Draw(Graphics graphic)
{
}
public GeneralMementor CreateMemento()
{
GeneralMementor memento = new GeneralMementor();
memento.SetState(this);
return memento;
}
public void SetMemento(GeneralMementor memento)
{
Rectangle r = memento.GetState() as Rectangle;
SetValue(r);
}
}
public class GeneralMementor
{
MemoryStream rSaved = new MemoryStream();
internal void SetState(object obj)
{
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(rSaved, obj);
}
internal object GetState()
{
BinaryFormatter bf = new BinaryFormatter();
rSaved.Seek(0, SeekOrigin.End);
object obj = bf.Deserialize(rSaved);
return obj;
}
}
public class GraphicsSystem
{
//原發(fā)器對象:
//有必要對自身內(nèi)部狀態(tài)進(jìn)行保存,然后在某個點處又需要恢復(fù)內(nèi)部狀態(tài)的對象
Rectangle r = new Rectangle(0, 0, 10, 10);
GeneralMementor memntor = null;
//備忘錄對象:
//保存原發(fā)器對象的內(nèi)部狀態(tài),但不提供原發(fā)器對象支持的操作
public void Process(Rectangle r)
{
memntor = r.CreateMemento();
//....
}
public void Saved_Click(object sender, EventArgs e)
{
r.SetMemento(memntor);
//....
}
}
上面的代碼中我們可以看到將Rectangle原發(fā)器保存在內(nèi)存流里,在恢復(fù)的時候,將內(nèi)存流里的對象進(jìn)行還原,我們可以更進(jìn)一步的抽象,可以將對象保存在任何的流里,這里就不做演示了。
Examda提示:在不破壞封裝性的前提下,捕獲一個對象的內(nèi)部狀態(tài),并在該對象之外保存這個狀態(tài)。這樣以后就可以將該對象恢復(fù)到原先保存的狀態(tài)。
我們首先看看不適用設(shè)計模式來解決對象狀態(tài)恢復(fù)的情況。
public class Rectangle : ICloneable
{
int x;
int y;
int width;
int height;
public void SetValue(Rectangle r)
{
this.x = r.x;
this.y = r.y;
this.width = r.width;
this.height = r.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 ChangeWidth(int width)
{
}
public void ChangeHeight(int height)
{
}
public void Draw(Graphics graphic)
{
}
#region ICloneable 成員
public object Clone()
{
return this.MemberwiseClone();
}
#endregion
}
public class GraphicsSystem
{
//原發(fā)器對象:
//有必要對自身內(nèi)部狀態(tài)進(jìn)行保存,然后在某個點處又需要恢復(fù)內(nèi)部狀態(tài)的對象
Rectangle r = new Rectangle(0, 0, 10, 10);
//備忘錄對象:
//保存原發(fā)器對象的內(nèi)部狀態(tài),但不提供原發(fā)器對象支持的操作
Rectangle rSaved = new Rectangle(0, 0, 10, 10);
public void Process()
{
rSaved = r.Clone();
//....
}
public void Saved_Click(object sender, EventArgs e)
{
r.SetValue(rSaved);
//....
}
}
class Program
{
static void Main(string[] args)
{
Rectangle r = new Rectangle(0, 0, 10, 10);
GraphicsSystem g = new GraphicsSystem();
g.Process(r);
}
}
上面的代碼中Rectangle類實現(xiàn)了ICloneable接口,這個接口利用淺拷貝返回一個新的Rectangle對象
在GraphicsSystem類中,我們定義了一個原發(fā)器對象r,和備忘錄對象rSaved,在Process的時候,我們將原發(fā)器對象進(jìn)行克隆保存在rSaved引用中。在Saved_Click方法中,將備忘錄對象rSaved保存的值還原給原發(fā)器對象r。但這樣來做,備忘錄對象提過了原發(fā)器對象的一些操作,那么我們現(xiàn)在需要將備忘錄對象抽象出來。
public class Rectangle
{
int x;
int y;
int width;
int height;
public void SetValue(Rectangle r)
{
this.x = r.x;
this.y = r.y;
this.width = r.width;
this.height = r.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 ChangeWidth(int width)
{
}
public void ChangeHeight(int height)
{
}
public void Draw(Graphics graphic)
{
}
internal RectangleMemento Creatememento()
{
RectangleMemento memento = new RectangleMemento();
memento.SetState(this.x, this.y, this.width, this.height);
return memento;
}
internal void SetMemento(RectangleMemento memento)
{
this.x = memento.x;
this.y = memento.y;
this.width = memento.width;
this.height = memento.height;
}
}
internal class RectangleMemento
{
internal int x;
internal int 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;
}
}
public class GraphicsSystem
{
//原發(fā)器對象:
//有必要對自身內(nèi)部狀態(tài)進(jìn)行保存,然后在某個點處又需要恢復(fù)內(nèi)部狀態(tài)的對象
Rectangle r = new Rectangle(0, 0, 10, 10);
//備忘錄對象:
//保存原發(fā)器對象的內(nèi)部狀態(tài),但不提供原發(fā)器對象支持的操作
RectangleMemento rSaved = new RectangleMemento();
public void Process(Rectangle r)
{
rSaved = r.Creatememento();
//....
}
public void Saved_Click(object sender, EventArgs e)
{
r.SetMemento(rSaved);
//....
}
}
在上面這段代碼中,我們將備忘錄對象抽象出來為RectangleMemento類,這個類只保存了原發(fā)器對象基本的值,但沒有提供其他的操作,并且,我們將RectangleMemento類和內(nèi)部成員全部申明為internal只能讓程序集本身調(diào)用,保證了RectangleMemento對象的封裝性。
實現(xiàn)要點:
備忘錄存儲原發(fā)器(Originator)對象的內(nèi)部狀態(tài),在需要時恢復(fù)原發(fā)器狀態(tài)。Memento模式適用于由原發(fā)器管理,卻又必須存儲在原發(fā)器之外的信息
在實現(xiàn)Memento模式中,要防止原發(fā)器以外的對象方位備忘錄對象,備忘錄對象有兩個接口,一個為原發(fā)器使用的寬接口,一個為其他對象使用的窄接口
在實現(xiàn)Memento模式時,要考慮拷貝對象狀態(tài)的效率問題,如果對象開銷比較大,可以采用某種增量式改變來跟進(jìn)Memnto模式
在上面的例子中Rectangle對于RectangleMemento看到是寬接口,即SetState方法,而GraphicsSystem看到的是窄接口,即RectangleMemento的構(gòu)造函數(shù)和Creatememento、SetMemento方法?!‘?dāng)一個對象比較大的時候,在.net中如DataSet,要保存對象的狀態(tài)可能會造成效率問題,占用比較大的內(nèi)存。
下面一段代碼演示了通過使用序列化的方式來實現(xiàn)Memento模式
[Serializable]
public class Rectangle
{
int x;
int y;
int width;
int height;
public void SetValue(Rectangle r)
{
this.x = r.x;
this.y = r.y;
this.width = r.width;
this.height = r.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 ChangeWidth(int width)
{
}
public void ChangeHeight(int height)
{
}
public void Draw(Graphics graphic)
{
}
public GeneralMementor CreateMemento()
{
GeneralMementor memento = new GeneralMementor();
memento.SetState(this);
return memento;
}
public void SetMemento(GeneralMementor memento)
{
Rectangle r = memento.GetState() as Rectangle;
SetValue(r);
}
}
public class GeneralMementor
{
MemoryStream rSaved = new MemoryStream();
internal void SetState(object obj)
{
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(rSaved, obj);
}
internal object GetState()
{
BinaryFormatter bf = new BinaryFormatter();
rSaved.Seek(0, SeekOrigin.End);
object obj = bf.Deserialize(rSaved);
return obj;
}
}
public class GraphicsSystem
{
//原發(fā)器對象:
//有必要對自身內(nèi)部狀態(tài)進(jìn)行保存,然后在某個點處又需要恢復(fù)內(nèi)部狀態(tài)的對象
Rectangle r = new Rectangle(0, 0, 10, 10);
GeneralMementor memntor = null;
//備忘錄對象:
//保存原發(fā)器對象的內(nèi)部狀態(tài),但不提供原發(fā)器對象支持的操作
public void Process(Rectangle r)
{
memntor = r.CreateMemento();
//....
}
public void Saved_Click(object sender, EventArgs e)
{
r.SetMemento(memntor);
//....
}
}
上面的代碼中我們可以看到將Rectangle原發(fā)器保存在內(nèi)存流里,在恢復(fù)的時候,將內(nèi)存流里的對象進(jìn)行還原,我們可以更進(jìn)一步的抽象,可以將對象保存在任何的流里,這里就不做演示了。