设计模式之C#实现(四)---- ProtoType

该模式的意图是:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。那么首先我们应该已经有了一个对象,同时这个对象还支持自我复制(科隆)。在 FCL 里面我们知道有一个接口专门用来规定这么一个契约,那就是 ICloneable 接口,该接口只有一个方法 Clone ,以下 MSDN 对该接口中对该接口的方法的说明:创建作为当前实例副本的新对象。 Clone 既可作为深层副本实现,也可作为浅表副本实现。在深层副本中,所有的对象都是重复的;而在浅表副本中,只有顶级对象是重复的,并且顶级以下的对象包含引用。结果克隆必须与原始实例具有相同的类型或是原始实例的兼容类型。这样一来如果我们想要一个可以自我复制的对象我们可以申明一个类 MyClass 该类继承接口 ICloneable 。

下面我们举一个显示生活中的例子来说明这种方法是普遍存在的的。我想我们可能都知道细胞,细胞是组成机体的最小单位(也许不是),我们知道任何(也许是部分)动物或者植物都是由一个细胞分裂的来的,所以我们可以理解细胞就是一种带有 Clone 方法的对象,细胞可以不断的调用该方法是我们的机体趋于完整。还可以举一个例子就是病毒,我们都经历过 Sars 期间,我想如果 Sars 没有自我复制的功能它就不可能成气候,还有一个人人都要实现的例子就是钱(我不知道是不是恰当),我们可以通过我们的努力使得 1 块钱变成 2 块 … 当然每个人的实现方法不同自然结果也就不同了 ( 不知道是否贴切 ) 。当然上面所据的前两个例子应该都是一种深层副本,因为他一旦被复制就是一个独立的个体,在内存中就是不同的两个地址,而潜表拷贝则不同,表面上看是两个实际上是一个对象的两个引用,也就是实际上他们存在于同一个地址,如果我们改变其中一个那么另一个也会改变。应此我们可以看出应用这种模式的关键就是如何实现 Clone 的方法。

下面是 PROTOTYPE 的结构图(来自 ROSE2003 ):

浅拷贝和深拷贝之间的区别:浅拷贝是指将对象中的数值类型的字段拷贝到新的对象中,而对象中的引用型字段则指复制它的一个引用到目标对象。如果改变目标对象中引用型字段的值他将反映在原是对象中,也就是说原始对象中对应的字段也会发生变化。深拷贝与浅拷贝不同的是对于引用的处理,深拷贝将会在新对象中创建一个新的和原是对象中对应字段相同(内容相同)的字段,也就是说这个引用和原是对象的引用是不同的,我们在改变新对象中的这个字段的时候是不会影响到原始对象中对应字段的内容。所以对于原型模式也有不同的两种处理方法:对象的浅拷贝和深拷贝。在 FCL 中的 System 命名空间下面有一个浅拷贝的方法叫: MemberwiseClone 它是 创建当前 Object 的浅表副本。在我们的例子里我想实现一个浅拷贝同时在实现一个深拷贝,这样可以加深理解。

下面是一个浅拷贝的例子:

using System;

namespace Prototype_Shallow{

// 因为我们在 FCL 里面已经有这样的接口所以我们就不定义新的 Prototype 了

public class ConcretePrototype1 : ICloneable{

private int m_ID;

public int ID{

get {

return this .m_ID;

}

}

public ConcretePrototype1( int id){

this .m_ID = id;

}

public object Clone(){

return this .MemberwiseClone();

}

}

public class ConcretePrototype2 : ICloneable{

private int m_ID;

public int ID

{

get

{

return this .m_ID;

}

}

public ConcretePrototype2( int id){

this .m_ID = id;

}

public object Clone(){

return this .MemberwiseClone();

}

}

}

我们具体的原型都继承了接口 ICloneable, 同时也实现了该接口里面唯一个一个方法 Clone 。我们可以在客户端这样创建对象 ConcretePrototype1 p1 = new ConcretePrototype1(1); ConcretePrototype1 c1 = (ConcretePrototype1)p1.Clone(); 首先我们创建了对象 p1 ,接下来我们用通过 p1 的科隆方法得到了对象 c1 ,这就是一种浅拷贝(因为 MemberwiseClone 是浅拷贝)。

接下来我们将要实现的是深拷贝,这个一个相对浅拷贝比较困难的工作,我们在拷贝对象的时候不但要拷贝他的数值型字段同时还要复制它的引用字段(有的时候这种工作是非常困难的也许是不可能的)。

namespace Prototype_Deep{

using System.Collections;

public class ConcretePrototype : ICloneable

{

private int m_ID;

public int ID

{

get

{

return this .m_ID;

}

}

private ArrayList m_arrayList = new ArrayList();

public ConcretePrototype( int id)

{

this .m_ID = id;

this .m_arrayList.Add("FirstObject");

this .m_arrayList.Add("SecondObject");

// ...

}

public object Clone()

{

ConcretePrototype c = new ConcretePrototype( this .ID);

c.m_arrayList = new ArrayList();

c .m_arrayList.Add("FirstObject");

c .m_arrayList.Add("SecondObject");

return c;

}

public ConcretePrototype DeepClone(){

return (ConcretePrototype) this .Clone();

}

}

}

该代码显示了如何实现深拷贝,深拷贝的原则就是对于那些引用的字段您需要 new ( new 之前想想是不是能用前面学过的某个创建型的模式实现,这是一个好的习惯)一个出来,然后对该字段里面的对象一一拷贝,这样以来很容易出现循环拷贝,所以说深拷贝要比浅拷贝更难一些。客户端可以通过 ConcretePrototype p = new ConcretePrototype(1);ConcretePrototype c = p.DeepClone(); 来实现克隆一个新的对象。代码如下:

ConcretePrototype p = new ConcretePrototype(1);

ConcretePrototype c = p.DeepClone();

this .richTextBox1.AppendText(p.ToString()+":"+p.ID.ToString()+"\n");

this .richTextBox1.AppendText(c.ToString()+":"+c.ID.ToString()+"\n");

c.m_arrayList[0] = "Changed";

for ( int i = 0;i<=1;i++){

this .richTextBox1.AppendText(c.m_arrayList[i].ToString());

}

this .richTextBox1.AppendText("\n");

for ( int i = 0;i<=1;i++){

this .richTextBox1.AppendText(p.m_arrayList[i].ToString());

}

这样我们将看到结果我们在改变科隆出来的对象的时候原来的对象是不变的。前面的浅拷贝我们有做这种比较,有兴趣的读者可以试一试,在 FCL 中大部分的科隆都是浅拷贝,我们可以看看实现 ICloneable 接口的类,几乎全部都是浅拷贝。顺便说一下在工作流中关于处理 (Process) 对象以及其他的相关对象我们可以使用这种 Clone 的方法,这个您可以参看 CRM 的 WorkFlow 中的类,又很多都实现了该方法。

希望我的文章可以帮助您很好的理解设计模式中的原型,关于该模式中的详细信息您可以参看 GOF 的书上面说得很清楚。在这次实现中我们利用了 FCL 的接口,没有自己写接口但是效果是一样的。如果在文章中出现什么不对的地方忘网友指正: [email protected] 谢谢您的支持,希望我们可以共同进步。

Published At
Categories with Web编程
Tagged with
comments powered by Disqus