在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这样类的实例时,如果我们用new操作符去创建这样的类实例,这未免会增加创建类的复杂度和耗费更多的内存空间,因为这样在内存中分配了多个一样的类实例对象,然后如果采用工厂模式来创建这样的系统的话,随着产品类的不断增加,导致子类的数量不断增多,反而增加了系统复杂程度,所以在这里使用工厂模式来封装类创建过程并不合适,然而原型模式可以很好地解决这个问题,因为每个类实例都是相同的,当我们需要多个相同的类实例时,没必要每次都使用new运算符去创建相同的类实例对象,此时我们一般思路就是想——只创建一个类实例对象,如果后面需要更多这样的实例,可以通过对原来对象拷贝一份来完成创建,这样在内存中不需要创建多个相同的类实例,从而减少内存的消耗和达到类实例的复用。 然而这个思路正是原型模式的实现方式。下面就具体介绍下设计模式中的原型设计模式。
一、 原型(Prototype)模式
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
创建型模式中一个比较特殊的模式-原型模式,有个最大的特点是克隆一个现有的对象,这个克隆的结果有2种,一种是浅度复制,另一种是深度复制。
创建型模式一般是用来创建一个新的对象,然后我们使用这个对象完成一些对象的操作,我们通过原型模式可以快速的创建一个对象而不需要提供专门的new()操作就可以快速完成对象的创建,这无疑是一种非常有效的方式,快速的创建一个新的对象。
浅度复制:
先看原型模式的经典实现:定义一个接口, 用来表述所有的颜色对象接口
/// <summary>
/// 颜色接口
/// </summary>
public interface IColor
{
IColor Clone(); int Red { get; set; }
int Green { get; set; }
int Blue { get; set; }
}
给出红色的具体实现代码:
public class RedColor : IColor
{
public int Red { get; set; }
public int Green { get; set; }
public int Blue { get; set; } public IColor Clone()
{
return (IColor)this.MemberwiseClone();
}
}
具体的测试代码如下:
using System; static void Main(string[] args)
{
IColor color = new RedColor();
color.Red = ;
Console.WriteLine("color -red " + color.Red); //225
IColor color1 = color.Clone();
color1.Red = ;
Console.WriteLine("color1-red " + color1.Red); //224
Console.WriteLine("color -red " + color.Red); //225
}
运行结果如下:
可以发现:在我们修改color1对象的Red属性值,没有对color的属性参生影响。
即对象副本的修改不会影响对象本身的状态。
深度复制:
深复制考虑的情况相对来说就会比较复杂,因为有可能对象是之间有继承关系或者引用关系的时候,可能我们深复制的时候就需要注意
一般来说深复制一方面可以采用种简单的深复制对象的时候的方案,还可以通过序列化的形式来进行对象的复制。
下面通过序列化的形式来实现原型模式:
using System; namespace ConsoleApplication4
{
/// <summary>
/// 颜色接口
/// </summary>
public interface IColor
{
IColorDemo Clone(); int Red { get; set; }
int Green { get; set; }
int Blue { get; set; }
Factroy f { get; set; }
} /// <summary>
/// 生产颜色的工厂信息
/// </summary>
[Serializable]
public class Factroy
{
public string Name { get; set; }
}
}
using System; namespace ConsoleApplication4
{
/// <summary>
/// 颜色
/// </summary>
[Serializable]
public class RedColor : IColor
{
public int Red { get; set; }
public int Green { get; set; }
public int Blue { get; set; }
public Factroy F { get; set; } public IColor Clone()
{
SerializableHelper s = new SerializableHelper();
string target = s.Serializable(this);
return s.Derializable<IColor>(target);
}
}
}
using System;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary; /// <summary>
/// 序列化和反序列化辅助类
/// </summary>
public class SerializableHelper
{
public string Serializable(object target)
{
using (var stream = new MemoryStream())
{
new BinaryFormatter().Serialize(stream, target); return Convert.ToBase64String(stream.ToArray());
}
} public object Derializable(string target)
{
byte[] targetArray = Convert.FromBase64String(target); using (var stream = new MemoryStream(targetArray))
{
return new BinaryFormatter().Deserialize(stream);
}
} public T Derializable<T>(string target)
{
return (T)Derializable(target);
}
}
using System; static void Main(string[] args)
{
IColor color = new RedColor();
color.Red = ;
color.F = new Factroy() { Name="湖北工厂" };
Console.WriteLine("color - Factroy:" + color.F.Name); //湖北工厂 IColor color1 = color.Clone();
color1.Red = ;
color1.F.Name = "北京工厂";
Console.WriteLine("color1- Factroy:" + color1.F.Name); //北京工厂
Console.WriteLine("color - Factroy:" + color.F.Name); //湖北工厂
Console.Read();
}
程序的运行结果如下:
结论:通过序列化和反序列化形成新的对象。其实只要是项目中要使用原型模式进行对象复制的情况下,都可以通过序列化的形式来进行深复制。
二、 原型模式的类图
三、 原型模式的实现
在现实生活中,也有很多原型设计模式的例子,例如,细胞分裂的过程,一个细胞的有丝分裂产生两个相同的细胞;还有西游记中孙悟空变出后孙的本领和火影忍者中鸣人的隐分身忍术等。下面就以孙悟空为例子来演示下原型模式的实现。具体的实现代码如下:
using System; ///火影忍者中鸣人的影分身和孙悟空的的变都是原型模式
class Client
{
private static void Main(string[] args)
{
// 孙悟空 原型
MonkeyKingPrototype prototypeMonkeyKing = new ConcretePrototype("MonkeyKing"); // 变一个
MonkeyKingPrototype cloneMonkeyKing = prototypeMonkeyKing.Clone() as ConcretePrototype;
if (cloneMonkeyKing != null) Console.WriteLine("Cloned1:\t" + cloneMonkeyKing.Id); // 变两个
MonkeyKingPrototype cloneMonkeyKing2 = prototypeMonkeyKing.Clone() as ConcretePrototype;
if (cloneMonkeyKing2 != null) Console.WriteLine("Cloned2:\t" + cloneMonkeyKing2.Id);
Console.ReadLine();
}
} /// <summary>
/// 孙悟空原型
/// </summary>
public abstract class MonkeyKingPrototype
{
public string Id { get; set; } protected MonkeyKingPrototype(string id)
{
this.Id = id;
} // 克隆方法,即孙大圣说“变”
public abstract MonkeyKingPrototype Clone();
} /// <summary>
/// 创建具体原型
/// </summary>
public class ConcretePrototype : MonkeyKingPrototype
{
public ConcretePrototype(string id)
: base(id)
{ } /// <summary>
/// 浅拷贝
/// </summary>
/// <returns></returns>
public override MonkeyKingPrototype Clone()
{
// 调用MemberwiseClone方法实现的是浅拷贝,另外还有深拷贝
return (MonkeyKingPrototype)this.MemberwiseClone();
}
}
上面原型模式的运行结果为(从运行结果可以看出,创建的两个拷贝对象的ID属性都是与原型对象ID属性一样的):
Cloned1: MonkeyKing
Cloned2: MonkeyKing
上面代码实现的浅拷贝的方式,浅拷贝是指当对象的字段值被拷贝时,字段引用的对象不会被拷贝。例如,如果一个对象有一个指向字符串的字段,并且我们对该对象做了一个浅拷贝,那么这两个对象将引用同一个字符串,而深拷贝是对对象实例中字段引用的对象也进行拷贝,如果一个对象有一个指向字符串的字段,并且我们对该对象进行了深拷贝的话,那么我们将创建一个对象和一个新的字符串,新的对象将引用新的字符串。也就是说,执行深拷贝创建的新对象和原来对象不会共享任何东西,改变一个对象对另外一个对象没有任何影响,而执行浅拷贝创建的新对象与原来对象共享成员,改变一个对象,另外一个对象的成员也会改变。
四、 .NET中原型模式的实现
在.NET中可以很容易地通过实现ICloneable接口(这个接口就是原型,提供克隆方法,相当于与上面代码中MonkeyKingPrototype抽象类)中Clone()方法来实现原型模式,如果我们想我们自定义的类具有克隆的功能,首先定义类继承与ICloneable接口并实现Clone方法。在.NET中实现了原型模式的类如下图所示(图中只截取了部分,可以用Reflector反编译工具进行查看):
五、 原型模式的优缺点
优点:
- 原型模式向客户隐藏了创建新实例的复杂性
- 原型模式允许动态增加或较少产品类。
- 原型模式简化了实例的创建结构,工厂方法模式需要有一个与产品类等级结构相同的等级结构,而原型模式不需要这样。
- 产品类不需要事先确定产品的等级结构,因为原型模式适用于任何的等级结构
缺点:
- 每个类必须配备一个克隆方法
- 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
六、 总结
到这里关于原型模式的介绍就结束了,原型模式用一个原型对象来指明所要创建的对象类型,然后用复制这个原型对象的方法来创建出更多的同类型对象,它与工厂方法模式的实现非常相似,其中原型模式中的Clone方法就类似工厂方法模式中的工厂方法,只是工厂方法模式的工厂方法是通过new运算符重新创建一个新的对象(相当于原型模式的深拷贝实现),而原型模式是通过调用MemberwiseClone方法来对原来对象进行拷贝,也就是复制,同时在原型模式优点中也介绍了与工厂方法的区别(第三点)。