前言
原型模式其实C# Object中已经提供了一个Clone( )方法,平时很少用到,最近读Retrofit源码时候看到有这种使用方式。
定义
原型模式就是在系统clone()标记的基础上,对Clone()进行复写,不同的操作可以产生两种拷贝模式。
例子一场景 是个java的, 只是介绍使用的场景
UML类图
今天我们来讲原型模式,这个模式的简单程度是仅次于单例模式和迭代器模式,非常简单,但是要使
用好这个模式还有很多注意事项。我们通过一个例子来解释一下什么是原型模式。
现在电子账单越来越流行了,比如你的信用卡,到月初的时候银行就会发一份电子邮件到你邮箱中,
说你这个月消费了多少,什么时候消费的,积分是多少等等,这个是每个月发一次,但是还有一种也是银
行发的邮件你肯定有印象:广告信,现在各大银行的信用卡部门都在拉拢客户,电子邮件是一种廉价、快
捷的通讯方式,你用纸质的广告信那个费用多高呀,比如我今天推出一个信用卡刷卡抽奖活动,通过电子
账单系统可以一个晚上发送给 600 万客户,为什么要用电子账单系统呢?直接找个发垃圾邮件不就解决问
题了吗?是个好主意,但是这个方案在金融行业是行不通的,银行发这种邮件是有要求的,一是一般银行
都要求个性化服务,发过去的邮件上总有一些个人信息吧,比如“XX 先生”,“XX 女士”等等,二是邮件的
到达率有一定的要求,由于大批量的发送邮件会被接收方邮件服务器误认是垃圾邮件,因此在邮件头要增
加一些伪造数据,以规避被反垃圾邮件引擎误认为是垃圾邮件;从这两方面考虑广告信的发送也是电子账单系统(电子账单系统一般包括:账单分析、账单生成器、广告信管理、发送队列管理、发送机、退信处
理、报表管理等)的一个子功能,我们今天就来考虑一下广告信这个模块是怎么开发的。那既然是广告信,
肯定需要一个模版,然后再从数据库中把客户的信息一个一个的取出,放到模版中生成一份完整的邮件,
然后扔给发送机进行发送处理,我们来看类图:
例子1孙悟空的浅复制
///火影忍者中鸣人的影分身和孙悟空的的变都是原型模式 class Client { static void Main(string[] args) { // 孙悟空 原型 MonkeyKingPrototype prototypeMonkeyKing = new ConcretePrototype("MonkeyKing"); // 变一个 MonkeyKingPrototype cloneMonkeyKing = prototypeMonkeyKing.Clone() as ConcretePrototype; Console.WriteLine("Cloned1:\t"+cloneMonkeyKing.Id); // 变两个 MonkeyKingPrototype cloneMonkeyKing2 = prototypeMonkeyKing.Clone() as ConcretePrototype; Console.WriteLine("Cloned2:\t" + cloneMonkeyKing2.Id); Console.ReadLine(); } } /// <summary> /// 孙悟空原型 /// </summary> public abstract class MonkeyKingPrototype { public string Id { get; set; } public 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属性一样的):
上面代码实现的浅拷贝的方式,浅拷贝是指当对象的字段值被拷贝时,字段引用的对象不会被拷贝。例如,如果一个对象有一个指向字符串的字段,并且我们对该对象做了一个浅拷贝,那么这两个对象将引用同一个字符串,而深拷贝是对对象实例中字段引用的对象也进行拷贝,如果一个对象有一个指向字符串的字段,并且我们对该对象进行了深拷贝的话,那么我们将创建一个对象和一个新的字符串,新的对象将引用新的字符串。也就是说,执行深拷贝创建的新对象和原来对象不会共享任何东西,改变一个对象对另外一个对象没有任何影响,而执行浅拷贝创建的新对象与原来对象共享成员,改变一个对象,另外一个对象的成员也会改变。
介绍完原型模式的实现代码之后,下面看下原型模式的类图,通过类图来理清原型模式实现中类之间的关系。具体类图如下:
三、原型模式的优缺点
原型模式的优点有:
- 原型模式向客户隐藏了创建新实例的复杂性
- 原型模式允许动态增加或较少产品类。
- 原型模式简化了实例的创建结构,工厂方法模式需要有一个与产品类等级结构相同的等级结构,而原型模式不需要这样。
- 产品类不需要事先确定产品的等级结构,因为原型模式适用于任何的等级结构
原型模式的缺点有:
- 每个类必须配备一个克隆方法
- 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
四、.NET中原型模式的实现
在.NET中可以很容易地通过实现ICloneable接口(这个接口就是原型,提供克隆方法,相当于与上面代码中MonkeyKingPrototype抽象类)中Clone()方法来实现原型模式,如果我们想我们自定义的类具有克隆的功能,首先定义类继承与ICloneable接口并实现Clone方法。在.NET中实现了原型模式的类如下图所示(图中只截取了部分,可以用Reflector反编译工具进行查看):
例子2 孙悟空的深复制
分析:
孙悟空:根据自己的形状复制(克隆)出多个身外身
软件开发:通过复制一个原型对象得到多个与原型对象一模一样的新对象
工作原理:将一个原型对象传给要发动创建的对象(即客户端对象),这个要发动创建的对象通过请求原型对象复制自己来实现创建过程
创建新对象(也称为克隆对象)的工厂就是原型类自身,工厂方法由负责复制原型对象的克隆方法来实现
通过克隆方法所创建的对象是全新的对象,它们在内存中拥有新的地址,每一个克隆对象都是独立的
通过不同的方式对克隆对象进行修改以后,可以得到一系列相似但不完全相同的对象
原型模式的结构
原型模式包含以下3个角色:
?Prototype(抽象原型类)
?ConcretePrototype(具体原型类)
?Client(客户类)
浅克隆与深克隆
浅克隆(Shallow Clone):当原型对象被复制时,只复制它本身和其中包含的值类型的成员变量,而引用类型的成员变量并没有复制
深克隆(Deep Clone):除了对象本身被复制外,对象所包含的所有成员变量也将被复制
[Serializable] //深克隆时需要将类标记为Serializable public class Person:ICloneable { public string CurrentEmployee { get; set; } public Member Member { get; set; } public Person() { this.CurrentEmployee = "admin"; Member member = new Member(); member.Id = 3; member.Name = "Mem"; this.Member = member; } public object Clone() { return this.MemberwiseClone(); } #region 静态方式创建对象 private static Person _person; /// <summary> /// 静态构造函数,永远只运行一次 /// </summary> static Person() { _person = new Person(); } public static Person StaticClone() { return _person.MemberwiseClone() as Person; } #endregion }
[Serializable] public class Member { public int Id { get; set; } public string Name { get; set; } }
public class SerializeHelper { /// <summary> /// 序列化 /// </summary> /// <param name="target"></param> /// <returns></returns> public static string Serializable(object target) { using(MemoryStream stream=new MemoryStream()) { new BinaryFormatter().Serialize(stream,target); return Convert.ToBase64String(stream.ToArray()); } } /// <summary> /// 反序列化 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="target"></param> /// <returns></returns> public static T Derializable<T>(string target) { byte[] targetArray = Convert.FromBase64String(target); using (MemoryStream stream = new MemoryStream(targetArray)) { return (T)(new BinaryFormatter().Deserialize(stream)); } } /// <summary> /// 合并反序列化与序列化 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="t"></param> /// <returns></returns> public static T DeepClone<T>(T t) { return Derializable<T>(Serializable(t)); } }
//浅表克隆,原来创建的对象值会改变 Person p = new Person(); Person p1 = p.Clone() as Person; p1.CurrentEmployee = "user"; p1.Member.Id = 1; p1.Member.Name = "pp1"; //深度克隆,原来创建的对象值不会改变 Person p2 = p.Clone() as Person; Person p3 = SerializeHelper.Derializable<Person>(SerializeHelper.Serializable(p2)); //或者简写 //Person p3 = SerializeHelper.DeepClone<Person>(p2); p3.Member.Id = 6; p3.Member.Name = "dd3"; Console.Read();
例子3:原型模式应用
首先从实际生活来了解原型模式的由来,假设你有一份非常好的讲义,你的朋友也想要一份,那么怎么办?重新手抄一份?显然不是,当然是用复印机复印一份来得方便、直接,并且准确性也高,这种用原型来复制而不是重新创建的思维方式就是原型模式的核心思想。
Prototype Pattern也是一种创建型模式,它关注的是大量相同或相似对象的创建问题。应用原型模式就是建立一个原型,然后通过对原型来进行复制的方法,来产生一个和原型相同或相似的新对象,或者说用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
原型模式参与者:
? Prototype:原型类,声明一个Clone自身的接口;
? ConcretePrototype:具体原型类,实现一个Clone自身的操作。
在原型模式中,Prototype通常提供一个包含Clone方法的接口,具体的原型ConcretePrototype使用Clone方法完成对象的创建。
/// <summary> /// The ‘Prototype‘ abstract class /// </summary> public abstract class Prototype { private string _id; /// <summary> /// Constructor /// </summary> public Prototype(string id) { this._id = id; } /// <summary> /// Gets id /// </summary> public string Id { get { return _id; } } public abstract Prototype Clone(); }
ConcretePrototype1.cs
public class ConcretePrototype1 : Prototype { /// <summary> /// Constructor /// </summary> public ConcretePrototype1(string id) : base(id) { } /// <summary> /// Returns a shallow copy /// </summary> /// <returns></returns> public override Prototype Clone() { return (Prototype)this.MemberwiseClone(); } }
ConcretePrototype2.cs
public class ConcretePrototype2 : Prototype { /// <summary> /// Constructor /// </summary> public ConcretePrototype2(string id) : base(id) { } /// <summary> /// Returns a shallow copy /// </summary> /// <returns></returns> public override Prototype Clone() { return (Prototype)this.MemberwiseClone(); } }
Client.cs
static void Main(string[] args) { // Create two instances and clone each ConcretePrototype1 p1 = new ConcretePrototype1("I"); ConcretePrototype1 c1 = (ConcretePrototype1)p1.Clone(); Console.WriteLine("Cloned: {0}", c1.Id); ConcretePrototype2 p2 = new ConcretePrototype2("II"); ConcretePrototype2 c2 = (ConcretePrototype2)p2.Clone(); Console.WriteLine("Cloned: {0}", c2.Id); }
Cloned: I Cloned: II 请按任意键继续. .
原型模式的实践
ColorPrototype.cs
/// <summary> /// The ‘Prototype‘ abstract class /// </summary> public abstract class ColorPrototype { public abstract ColorPrototype Clone(); }
Color.cs
/// <summary> /// The ‘ConcretePrototype‘ class /// </summary> public class Color : ColorPrototype { private int _red; private int _green; private int _blue; /// <summary> /// Constructor /// </summary> public Color(int red, int green, int blue) { this._red = red; this._green = green; this._blue = blue; } /// <summary> /// Create a shallow copy /// </summary> public override ColorPrototype Clone() { Console.WriteLine("Cloning color RGB: {0,3},{1,3},{2,3}", _red, _green, _blue); return this.MemberwiseClone() as ColorPrototype; } }
ColorManager.cs
/// <summary> /// Prototype manager /// </summary> public class ColorManager { private Dictionary<string, ColorPrototype> _colors = new Dictionary<string, ColorPrototype>(); /// <summary> /// Indexer /// </summary> public ColorPrototype this[string key] { get { return _colors[key]; } set { _colors.Add(key, value); } } }
Client.cs
static void Main(string[] args) { ColorManager colormanager = new ColorManager(); // Initialize with standard colors colormanager["red"] = new Color(255, 0, 0); colormanager["green"] = new Color(0, 255, 0); colormanager["blue"] = new Color(0, 0, 255); // User adds personalized colors colormanager["angry"] = new Color(255, 54, 0); colormanager["peace"] = new Color(128, 211, 128); colormanager["flame"] = new Color(211, 34, 20); // User clones selected colors Color color1 = colormanager["red"].Clone() as Color; Color color2 = colormanager["peace"].Clone() as Color; Color color3 = colormanager["flame"].Clone() as Color; }
输出
Cloning color RGB: 255, 0, 0 Cloning color RGB: 128,211,128 Cloning color RGB: 211, 34, 20 请按任意键继续. . .
5、原型模式应用分析
原型模式可以适用于以下情形:
? 当一个系统应该独立于它的产品创建、构成和表示时;
? 当要实例化的类是在运行时刻指定时,例如通过动态装载来创建一个类;
? 为了避免创建一个与产品类层次平行的工厂类层次时;
? 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并Clone它们可能比每次用合适的状态手工实例化该类更方便一些。
原型模式具有以下特点:
? 对客户隐藏了具体的产品类,因此减少了客户知道的名字的数目;
? 允许客户只通过注册原型实例就可以将一个具体产品类并入到系统中,客户可以在运行时刻建立和删除原型;
? 减少了子类的构造。原型模式是Clone一个原型而不是请求工厂方法创建一个,所以它不需要一个与具体产品类平行的Creator类层次;
? 原型模式具有给一个应用软件动态加载新功能的能力。由于Prototype的独立性较高,可以很容易动态加载新功能而不影响旧系统;
? 产品类不需要非得有任何事先确定的等级结构,因为原型模式适用于任何的等级结构;
? 原型模式的最重要缺点就是每一个类必须配备一个Clone方法,而且这个Clone方法需要对类的功能进行通盘考虑。这对全新的类来说不是很难,但对已有的类进行改造时,不一定是容易的事。
本文资料来自互联网整理,为方便大家更多的了解设计模式,若有侵权等本文整理尽快下架.
原文地址:https://www.cnblogs.com/zuochanzi/p/8900507.html