原型模式:使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象。
《西游记》中,孙悟空可以根据自己的形状复制(克隆)出多个身外身,如图1所示,这种技巧在面向对象软件设计领域被称之为原型模式,孙悟空被称之为原型对象。原型模式通过复制一个原型对象得到多个与原型对象一模一样的新对象。
图1 孙悟空复制身外身
原型模式结构如图2所示:
图2 原型模式结构图
实现原型模式的关键在于如何实现克隆方法,不同的编程语言提供了不同的克隆方法实现机制,下面介绍两种在C#语言中常用的克隆实现方法。
1. 通用实现方法
通用的克隆实现方法是在具体原型类的克隆方法中实例化一个与自身类型相同的对象并将其返回,并将相关的参数传入新创建的对象中,保证它们的成员变量相同。示意代码如下:
abstract class Prototype
{
public abstract Prototype Clone();
}
class ConcretePrototype : Prototype
{
private string attr; //成员变量
public string Attr
{
get { return attr; }
set { attr = value; }
}
//克隆方法
public override Prototype Clone()
{
ConcretePrototype prototype = new ConcretePrototype();
prototype.Attr = attr;
return prototype;
}
}
在客户类中只需要创建一个ConcretePrototype对象作为原型对象,然后调用其Clone()方法即可得到对应的克隆对象,如下代码片段所示:
……
ConcretePrototype prototype = new ConcretePrototype();
ConcretePrototype copy = (ConcretePrototype)prototype.Clone();
……
此方法是原型模式的通用实现,它与编程语言本身的特性无关,除C#外,其他面向对象编程语言也可以使用这种形式来实现对原型的克隆。
在原型模式的通用实现方法中,可通过手工编写Clone()方法来实现浅克隆和深克隆。对于引用类型的对象,可以在Clone()方法中通过赋值的方式来实现复制,这是一种浅克隆实现方案;如果在Clone()方法中通过创建一个全新的成员对象来实现复制,则是一种深克隆实现方案。C#语言中的字符串(string/String)对象存在特殊性,只要两个字符串的内容相同,无论是直接赋值还是创建新对象,它们在内存中始终只有一份。如需进一步了解,大家可查阅“C#字符串驻留机制”相关资料。
2. C#中的MemberwiseClone()方法和ICloneable接口
在C#语言中,提供了一个MemberwiseClone()方法用于实现浅克隆,该方法使用起来很方便,直接调用一个已有对象的MemberwiseClone()方法即可实现克隆。如下代码所示:
//成员类
class Member
{
}
class ConcretePrototypeA
{
private Member member; //成员对象
public Member Member
{
get { return member; }
set { member = value; }
}
//克隆方法
public ConcretePrototypeA Clone()
{
return (ConcretePrototypeA)this.MemberwiseClone(); //浅克隆
}
}
在客户类中可以直接调用原型对象的Clone()方法来创建新的对象,如下代码片段所示:
……
ConcretePrototypeA prototype, copy;
prototype = new ConcretePrototypeA();
copy = prototype.Clone();
Console.WriteLine(prototype == copy);
Console.WriteLine(prototype.Member == copy.Member);
……
在上述客户类代码片段中,输出语句“Console.WriteLine(prototype == copy);”的输出结果为“False”,输出语句“Console.WriteLine(prototype.Member == copy.Member);”的输出结果为“True”,表明此处的克隆方法为浅克隆。
除了MemberwiseClone()方法,在C#语言中还提供了一个ICloneable接口,它也可以用来创建当前对象的拷贝,其代码如下:
public interface ICloneable
{
object Clone();
}
ICloneable接口充当了抽象原型类的角色,具体原型类通常作为实现该接口的子类,如下代码所示:
class ConcretePrototypeB : ICloneable //实现ICloneable接口
{
private Member member;
public Member Member
{
get { return member; }
set { member = value; }
}
//实现深克隆
public object Clone()
{
ConcretePrototypeB copy = (ConcretePrototypeB)this.MemberwiseClone();
Member newMember = new Member();
copy.Member = newMember;
return copy;
}
}
客户类代码片段如下:
……
ConcretePrototypeB prototype, copy;
prototype = new ConcretePrototypeB();
copy = (ConcretePrototypeB) prototype.Clone();
Console.WriteLine(prototype == copy);
Console.WriteLine(prototype.Member == copy.Member);
……
在此客户类代码片段中,输出语句“Console.WriteLine(prototype == copy);”的输出结果为“False”,输出语句“Console.WriteLine(prototype.Member == copy.Member);”的输出结果也为“False”,表明此处的克隆方法为深克隆。
在实现ICloneable接口时,通常提供的是除MemberwiseClone()以外的深克隆方法。除了通过直接创建新的成员对象来手工实现深克隆外,还可以通过反射、序列化等方式来实现深克隆,在使用序列化实现时要求所有被引用的对象都必须是可序列化的(Serializable)。
下面介绍一下如何使用C#的序列化机制来实现深克隆,使用序列化实现深克隆包含两个步骤。
首先必须将具体原型类(ConcretePrototype)和成员类(Member)标记为可序列化(Serializable),如下所示:
[Serializable]
class ConcretePrototype
{
private Member member;
……
}
[Serializable]
class Member
{
……
}
然后将具体原型类(ConcretePrototype)的Clone()方法修改如下:
//使用序列化方式实现深克隆
public ConcretePrototype Clone()
{
ConcretePrototype clone = null;
FileStream fs = new FileStream("Temp.dat", FileMode.Create);
BinaryFormatter formatter = new BinaryFormatter();
try
{
formatter.Serialize(fs, this); //序列化
}
catch (SerializationException e)
{
Console.WriteLine("Failed to serialize. Reason: " + e.Message);
throw;
}
finally
{
fs.Close();
}
FileStream fs1 = new FileStream("Temp.dat", FileMode.Open);
BinaryFormatter formatter1 = new BinaryFormatter();
try
{
clone = (ConcretePrototype)formatter1.Deserialize(fs1); //反序列化
}
catch (SerializationException e)
{
Console.WriteLine("Failed to deserialize. Reason: " + e.Message);
throw;
}
finally
{
fs1.Close();
}
return clone;
}
上述代码的Clone()方法中,在成功复制原型对象的同时成员对象也被复制,实现了深克隆。
在上述深克隆实现代码中,通过使用FileStream类和BinaryFormatter类可实现对象的序列化和反序列化操作,首先使用序列化将当前对象写入流中,然后再使用反序列化从流中获取对象。由于在序列化时一个对象的成员对象将伴随该对象一起被写入流中,在反序列化时将得到一个包含成员对象的新对象,因此可采用序列化和反序列化联用来实现深克隆。
【作者:刘伟 http://blog.csdn.net/lovelion】