前言
原型模式其实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: "+cloneMonkeyKing.Id); // 变两个 MonkeyKingPrototype cloneMonkeyKing2 = prototypeMonkeyKing.Clone() as ConcretePrototype; Console.WriteLine("Cloned2: " + 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 孙悟空的深复制
分析:
[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
请按任意键继续. .
例4:原型模式的实践
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方法需要对类的功能进行通盘考虑。这对全新的类来说不是很难,但对已有的类进行改造时,不一定是容易的事。
例5:原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。 自己理解。定义好一个原型也就是初始对象类。根据这个去复制 大量的与原型一样或者属性类似。
public class CSDproto : MonoBehaviour { //怪物模型原型类 public abstract class Enemy { public string Name; public int Heath; public abstract Enemy Clone(); public abstract void Attack(); } //普通僵尸怪 public class zombieE : Enemy { private int speed; public override void Attack() { Debug.Log( Name+"开始攻击" + "Heath:" + Heath +"speed:"+speed); } public override Enemy Clone() { zombieE prototype = new zombieE(); prototype.speed = 1; prototype.Name = "普通僵尸"; prototype.Heath = 1000; return prototype; } } //炸弹僵尸 public class BomzombieE : Enemy { public override void Attack() { Debug.Log(Name + "开始攻击炸弹"); } public override Enemy Clone() { BomzombieE bz = new BomzombieE(); bz.Name = "炸弹僵尸"; bz.Heath = 1000; return bz; } } //----------创建僵尸 zombieE zb;//普通僵尸 BomzombieE bz;//炸弹僵尸 void Start() { //先创建一个僵尸原型 zb = new zombieE(); bz = new BomzombieE(); } void Update() { if (Input.GetKeyDown(KeyCode.A)) { Enemy TEMP = zb.Clone(); TEMP.Attack(); } else if (Input.GetKeyDown(KeyCode.S)) { Enemy TEMP = bz.Clone(); TEMP.Attack(); } } }
游戏制作会创建大量角色。怪物,技能,武器等。 这时候可以使用原型模式。配合对象池。后面我在研究。
以上用怪物为基本原型。
不同僵尸类继承公用属性和实现自己不同属性。方法。每个clone会创建一个对象。后期可以保存在对象池里去随时遍历。
例6:
1. 概述
通过复制一个已经存在的实例来创建一个新的实例。被复制的实例被称为原型,这个原型是可定制的。
2. 模式中的角色
2.1 抽象原型类(Abstract Prototype):提供一个克隆接口
2.2 具体原型类(Concrete Prototype): 及实现了克隆接口的具体原型类
3. 实例:求职网站上现在都支持多份简历,如果每创建一份简历都要从头至尾地填写一遍,那也是非常让人沮丧的事。其实针对我们的求职岗位的不同,不同的简历可能只要修改局部内容就可以了,而不用全部重新构建一份新的简历。复制一份简历,然后做局部修改是最让人省心的了!
类图解读
在.NET中,System命名空间已经为我们提供了一个ICloneable接口,它包含了一个方法Clone(),实现这个接口就完成了原型模式。
3.2 在写实现代码之前,先要理解一下深复制与浅复制。
3.2.1 浅复制:将原来对象中的所有字段逐个复制到一个新对象,如果字段是值类型,则简单地复制一个副本到新对象,改变新对象的值类型字段不会影响原对象;如果字段是引用类型,则复制的是引用,改变目标对象中引用类型字段的值将会影响原对象。例如, 如果一个对象有一个指向引用类型(如例子中的工作经历)的字段, 并且我们对该对象做了一个浅复制, 那麽两个对象将引用同一个引用(即同一段工作经历)。
3.2.2 深复制:与浅复制不同之处在于对引用类型的处理,深复制将新对象中引用类型字段指向复制过的新对象,改变新对象中引用的任何对象,不会影响到原来的对象中对应字段的内容。例如,如果一个对象有一个指向引用类型(如例子中的工作经历)的字段,并且对该对象做了一个深复制的话.我门将创建一个新的对象(即新的工作经历)。
3.3 简历的浅复制实现
/// <summary> /// 实现了ICloneable接口的简历类 /// </summary> public class Resume:ICloneable { public Resume() { mWorkExperience = new WorkExperience(); } private string mName; private string mSex; private int mAge; private WorkExperience mWorkExperience; public string Name { get { return mName; } set { mName = value; } } public string Sex { get { return mSex; } set { mSex = value; } } public int Age { get { return mAge; } set { mAge = value; } } /// <summary> /// 关联了一个引用类型 /// </summary> public WorkExperience WorkExperience { get { return mWorkExperience; } } public void SetWorkExperience(DateTime startDate, DateTime endDate, string company, string position) { this.mWorkExperience.Company = company; this.mWorkExperience.EndDate = endDate; this.mWorkExperience.StartDate = startDate; this.mWorkExperience.Position = position; } /// <summary> /// 实现ICloneable接口的Clone方法 /// </summary> /// <returns></returns> public object Clone() { // .Net 为我们提供的浅复制对象的方法 return this.MemberwiseClone(); } } /// <summary> /// 工作经历类 /// </summary> public class WorkExperience { public DateTime StartDate { get; set; } public DateTime EndDate { get; set; } public string Company { get; set; } public string Position { get; set; } }
下面是测试代码
[TestMethod] public void TestShallowCopy() { Resume myFirstResume = new Resume { Age = 29, Name = "Kevin Wang", Sex = "男", }; myFirstResume.SetWorkExperience(new DateTime(2006, 7, 1), new DateTime(2007, 7, 1), "My First Company", "Software Engineer"); Resume mySecondResume = (Resume)myFirstResume.Clone(); mySecondResume.SetWorkExperience(new DateTime(2007, 8, 1), new DateTime(2008, 8, 1), "My Second Company", "Software Engineer"); Resume myThirdResume = (Resume)myFirstResume.Clone(); myThirdResume.SetWorkExperience(new DateTime(2008, 8, 1), new DateTime(2009, 8, 1), "My Third Company", "Senior Software Engineer"); Assert.AreEqual("My First Company", myFirstResume.WorkExperience.Company); Assert.AreEqual("My Second Company", mySecondResume.WorkExperience.Company); Assert.AreEqual("My Third Company", myThirdResume.WorkExperience.Company); }
这里期望的是三个断言都能运行成功,但是却是失败的,原因是:由于我们使用的是浅复制,所以myFirstResume, mySecondResume 和 myThirdResume引用的是同一个对象,因此最终的结果是 三个简历的WorkExperience.Company都是“My Third Company".
3.4 简历的深复制实现
/// <summary> /// 实现了ICloneable接口的简历类 /// </summary> public class Resume : ICloneable { public Resume() { mWorkExperience = new WorkExperience(); } /// <summary> /// 这里使用一个私有的构造函数来对其连接到的引用类型进行复制 /// </summary> /// <param name="workExperience"></param> private Resume(WorkExperience workExperience) { this.mWorkExperience = (WorkExperience)workExperience.Clone(); } private string mName; private string mSex; private int mAge; private WorkExperience mWorkExperience; public string Name { get { return mName; } set { mName = value; } } public string Sex { get { return mSex; } set { mSex = value; } } public int Age { get { return mAge; } set { mAge = value; } } public WorkExperience WorkExperience { get { return mWorkExperience; } } /// <summary> /// 设置功过经历 /// </summary> /// <param name="startDate"></param> /// <param name="endDate"></param> /// <param name="company"></param> /// <param name="position"></param> public void SetWorkExperience(DateTime startDate, DateTime endDate, string company, string position) { this.mWorkExperience.Company = company; this.mWorkExperience.EndDate = endDate; this.mWorkExperience.StartDate = startDate; this.mWorkExperience.Position = position; } /// <summary> /// 实现ICloneable接口的Clone方法 /// </summary> /// <returns></returns> public object Clone() { // 这里不再使用MemberwiseClone方法进行复制了,而是新创建了一个全新的简历。它完全是在内部实现的,外部不用关心它的实现 Resume newResume = new Resume(this.mWorkExperience); newResume.mSex = this.mSex; newResume.mName = this.mName; newResume.mAge = this.mAge; return newResume; } } public class WorkExperience :ICloneable { public DateTime StartDate { get; set; } public DateTime EndDate { get; set; } public string Company { get; set; } public string Position { get; set; } public object Clone() { // 使用.Net 为我们提供的浅复制对象的方法,因为这里已经没有引用对象了(string虽然是引用类型,但.NET为我们做了特别处理,可以像值类型一样使用它)。 return this.MemberwiseClone(); } }
[TestMethod] public void TestDeepCopy() { Resume myFirstResume = new Resume { Age = 29, Name = "Kevin Wang", Sex = "男", }; myFirstResume.SetWorkExperience(new DateTime(2006, 7, 1), new DateTime(2007, 7, 1), "My First Company", "Software Engineer"); Resume mySecondResume = (Resume)myFirstResume.Clone(); mySecondResume.SetWorkExperience(new DateTime(2007, 8, 1), new DateTime(2008, 8, 1), "My Second Company", "Software Engineer"); Resume myThirdResume = (Resume)myFirstResume.Clone(); myThirdResume.SetWorkExperience(new DateTime(2008, 8, 1), new DateTime(2009, 8, 1), "My Third Company", "Senior Software Engineer"); Assert.AreEqual("My First Company", myFirstResume.WorkExperience.Company); Assert.AreEqual("My Second Company", mySecondResume.WorkExperience.Company); Assert.AreEqual("My Third Company", myThirdResume.WorkExperience.Company); }
运行测试,测试通过,这正是我们期望的结果。
4. 模式总结
4.1 优点
4.1.1 隐藏了对象的创建细节,对有些初始化需要占用很多资源的类来说,对性能也有很大提高。
4.1.2 在需要新对象时,可以使用Clone来快速创建创建一个,而不用使用new来构建。
4.2 缺点
4.2.1 每一个类都需要一个Clone方法,而且必须通盘考虑。对于深拷贝来说,每个关联到的类型都不许实现IClonable接口,并且每增加或修改一个字段是都需要更新Clone方法。
4.3 适用场景
4.3.1 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等
4.3.2 通过new产生一个对象需要非常繁琐的数据准备或访问权限,则可以使用原型模式
4.3.3 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用。
是通过深挖设计模式搜集例子,增加了自己的开发视野,越来越有意思.
本文资料来自互联网整理,为方便大家更多的了解设计模式,若有侵权等本文整理尽快下架.