总目录
前言
在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这样类的实例时,如果我们用new操作符去创建这样的类实例,这未免会增加创建类的复杂度和耗费更多的内存空间,因为这样在内存中分配了多个一样的类实例对象,然后如果采用工厂模式来创建这样的系统的话,随着产品类的不断增加,导致子类的数量不断增多,反而增加了系统复杂程度,所以在这里使用工厂模式来封装类创建过程并不合适,然而原型模式可以很好地解决这个问题,因为每个类实例都是相同的,当我们需要多个相同的类实例时,没必要每次都使用new运算符去创建相同的类实例对象,此时我们一般思路就是想——只创建一个类实例对象,如果后面需要更多这样的实例,可以通过对原来对象拷贝一份来完成创建,这样在内存中不需要创建多个相同的类实例,从而减少内存的消耗和达到类实例的复用。 然而这个思路正是原型模式的实现方式。
1 基本介绍
- 原型模式就是通过复制已有对象来创建新对象,而避免重复进行初始化操作,生产多个克隆对象。
- 本质:通过拷贝这些原型对象创建新的对象。
- 原型模式中的2个角色:
- Prototype(原型类):声明一个Clone自身的接口;
- ConcretePrototype(具体原型类):实现一个Clone自身的操作,使用Clone方法完成对象的创建
- 浅拷贝和深拷贝
-
浅拷贝:通过this.MemberWiseClone(),对实例的值类型进行拷贝(包含string类型),对引用类型只拷贝了引用。浅拷贝只对值类型成员进行复制,对于引用类型,只是复制了其引用,并不复制其对象。执行浅拷贝创建的新对象与原来对象共享成员,改变一个对象,另外一个对象的成员也会改变。
-
深拷贝:需要通过反射和序列化来实现,执行深拷贝创建的新对象和原来对象不会共享任何东西,改变一个对象对另外一个对象没有任何影响
-
2 使用场景
对象在创建(new)时,消耗资源过多或繁琐耗时。
本质就是在对象的构造函数中有耗时长或者占用系统资源多的情况,
使用原型模式进行复制对象时,可以省去这些耗时耗力的操作,直接获得对象的具体实例。
最常见的使用场景之一就是对象历史节点的保存,比如在对对象进行操作一次后,进行一次复制保存当前状态(恢复到某一历史状态),可实现撤销操作。
3 实现方式
在现实生活中,也有很多原型设计模式的例子,例如,细胞分裂的过程,一个细胞的有丝分裂产生两个相同的细胞;还有西游记中孙悟空变出后孙的本领和火影忍者中鸣人的隐分身忍术等。
1 抽象的原型类,定义一个复制自己的方法
public abstract class Prototype{//Id 值类型public int Id { get; set; }//Message string类型public string Message { get; set; }//NameList 引用类型public List<string> NameList { get; set; } = new List<string>();//构造函数public Prototype(){}//复制的方法public abstract Prototype Clone();}
2 具体的原型类,需要实现复制自己的方法
//具体的原型类public class ConcretePrototype : Prototype{public ConcretePrototype() : base(){}public override Prototype Clone(){// 调用MemberwiseClone方法实现的是浅拷贝,另外还有深拷贝return (Prototype)this.MemberwiseClone();}}
3 客户端调用
public static void Main(string[] args){// 首先创建一个原型类,并给相关属性赋值Prototype concretePrototype = new ConcretePrototype();concretePrototype.Id = 1;concretePrototype.Message = "消息:AAA";concretePrototype.NameList.Add("Jack");concretePrototype.NameList.Add("Marks");Console.WriteLine($"Id:{concretePrototype.Id}");Console.WriteLine($"Message:{concretePrototype.Message}");Console.WriteLine("NameList:");foreach ( string item in concretePrototype.NameList){Console.WriteLine($"Name:{item}");}Console.WriteLine("\r\n");// 然后通过 实现后的原型类 去 复制出一个对象// 然后给复制出的对象赋值,验证当前浅拷贝对于值类型和引用类型的影响Prototype concretePrototype2 = concretePrototype.Clone();concretePrototype2.Id = 2;concretePrototype2.Message = "消息:BBB";concretePrototype2.NameList[1] = "Jack Ma";Console.WriteLine($"复制的对象 Id:{concretePrototype2.Id}");Console.WriteLine($"复制的对象 Message:{concretePrototype2.Message}");Console.WriteLine("复制的对象 NameList:");foreach (string item in concretePrototype2.NameList){Console.WriteLine($"Name:{item}");}Console.WriteLine("\r\n");Console.WriteLine("更改了复制对象属性的值,对比值类型和类型类型的变化");Console.WriteLine($"Id:{concretePrototype.Id}");Console.WriteLine($"Message:{concretePrototype.Message}");Console.WriteLine("NameList:");foreach (string item in concretePrototype.NameList){Console.WriteLine($"Name:{item}");}Console.ReadLine();}
通过这个实例可以看出浅复制,对值类型和string进行全盘拷贝,对引用类型除string只拷贝了引用地址。
4 深拷贝的实现方式
//原型类[Serializable]public abstract class Prototype{//Id 值类型public int Id { get; set; }//Message string类型public string Message { get; set; }//NameList 引用类型public List<string> NameList { get; set; } = new List<string>();//构造函数public Prototype(){}//复制的方法public abstract Prototype Clone();}//具体的原型类[Serializable]public class ConcretePrototype : Prototype{public ConcretePrototype() : base(){}// 实现深拷贝public override Prototype Clone(){//创建一个内存流MemoryStream ms = new MemoryStream();//创建一个二进制序列化对象BinaryFormatter bf = new BinaryFormatter();//将当前对象序列化写入ms内存流中bf.Serialize(ms, this);//设置流读取的位置ms.Position = 0;//将流反序列化为Object对象return bf.Deserialize(ms) as Prototype;}}
执行深拷贝创建的新对象和原来对象不会共享任何东西,改变一个对象对另外一个对象没有任何影响
4 优缺点分析
-
优点:
- 原型模式向客户隐藏了创建新实例的复杂性
- 原型模式允许动态增加或较少产品类。
- 原型模式简化了实例的创建结构,工厂方法模式需要有一个与产品类等级结构相同的等级结构,而原型模式不需要这样。
- 产品类不需要事先确定产品的等级结构,因为原型模式适用于任何的等级结构
-
缺点:
- 每个类必须配备一个克隆方法
- 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。
结语
希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。
参考资料:
c#中原型模式详解
C#设计模式(6)-原型模式