原型模式是一种经常使用的创建型设计模式,简单地说就是通过克隆(Clone)一个现有对象创建新的对象。实现原型模式的关键是如何复制对象,特别是对多层次,结构复杂的对象如何进行复制,这里介绍使用序列化的方式实现对象复制的方法。
对象基本数据类型的复制很简单,只要对属性进行赋值就可以了,或者直接使用C#中的MemberwiseClone方法,就可以实现:
1 2 3 4 5 6 7 8 9 10 11 public class Workflow { public string Name { get; set; } public string Description { get; set; } public Workflow Clone_Memberwise() { return this.MemberwiseClone() as Workflow; } }
这种方法叫做浅复制:只复制基本数据类型,对于有引用的对象,只复制引用,并不复制引用的对象。但在很多情况下,我们需要对引用的对象也进行复制,并且把引用修改到复制的新对象。比如如果有一个“汽车”类,每个汽车有四个“轮子”,当我们复制一辆“汽车”的时候,需要同时复制“轮子”,并且这些“轮子”在新的“汽车”上。如果使用浅复制,就会出现两辆“汽车”的“轮子”是共用的,这显然不是我们希望的结果。这种情况下,我们需要使用深复制,也就是针对每个引用的对象也进行复制。实现深复制可以使用迭代的方法,在每个被引用的类都实现Clone方法,比如,克隆汽车时要逐个克隆轮子,然后组装到新的汽车上。如果在项目设计初期就使用这种方法,是可行的,我们可以在一开始就为所有的类设计Clone方法。但如果是在项目的维护期出现这种需求,这种方法设计的改动量可能是巨大的。一个现实的例子是计划编制系统,计划涉及的项目很多,编制计划的过程很复杂,用户可能希望复制上一年度的计划作为本年度计划的模板。这种情况下,我们希望只修改聚合实体类,而不修改或者少修改其它的类。我们可以用序列化的方法实现深复制:将需要复制的对象序列化为json或者xml,然后再反序列化为新的对象,这时所有引用对象也会被创建为新的副本。我们以一个工作流定义的例子来说明,工作流包括节点和连接,实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 using System.Collections.Generic; namespace ZL.DesignPattern.ProtoType.Demo { public class Workflow_v0 { public string Name { get; set; } public string Description { get; set; } public List<Node_v0> Nodes { get; set; } public List<Link_v0> Links { get; set; } public Workflow_v0() { Nodes = new List<Node_v0>(); Links = new List<Link_v0>(); } /// <summary> /// 使用序列化的方法实现深复制 /// </summary> /// <returns></returns> public Workflow_v0 Clone_Serialize() { //将实体类序列化为JSON var json = Newtonsoft.Json.JsonConvert.SerializeObject(this); //反序列化JSON Workflow_v0 newFlow = Newtonsoft.Json.JsonConvert.DeserializeObject<Workflow_v0>(json); return newFlow; } /// <summary> /// 使用Memberwise浅复制 /// </summary> /// <returns></returns> public Workflow_v0 Clone_Memberwise() { return this.MemberwiseClone() as Workflow_v0; } } }
使用这种方法,需要注意以下两点: 1、避免循环引用 上面的例子中,节点、连接没有对工作流的引用,因此,执行时不会出现问题,但在实际中,我们可能需要在节点中引用工作流,这种情况下,我们需要标识不需要序列化的属性:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 using Newtonsoft.Json; namespace ZL.DesignPattern.ProtoType.Demo { public class Node { /// <summary> /// 需要增加JsonIgnore避免循环引用 /// </summary> [JsonIgnore] public Workflow Parent { get; set; } public string Name { get; set; } public string NodeType { get; set; } } }
2、对象引用关系可能需要在克隆方法中进行组织 上面的例子中,如果连接中使用的不是节点的名称,而是节点的引用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 using System.Text; namespace ZL.DesignPattern.ProtoType.Demo { public class Link { public string Condition { get; set; } public Node Source { get; set; } public Node Target { get; set; } } }
我们采用序列化深复制后,连接关联的两个节点就不在流程的节点列表中,这种情况下,需要重新组织对象关系,下面是完整的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 using System.Collections.Generic; using System.Linq; namespace ZL.DesignPattern.ProtoType.Demo { public class Workflow { public string Name { get; set; } public string Description { get; set; } public List<Node> Nodes { get; set; } public List<Link> Links { get; set; } public Workflow() { Nodes = new List<Node>(); Links = new List<Link>(); } /// <summary> /// 最初的版本,仅序列化/反序列化就返回。序列化和反序列化不能区分相同的对象引用,因此会创建新的实例 /// </summary> /// <returns></returns> public Workflow Clone_Serialize() { //将实体类序列化为JSON var json = Newtonsoft.Json.JsonConvert.SerializeObject(this); //反序列化JSON Workflow newFlow = Newtonsoft.Json.JsonConvert.DeserializeObject<Workflow>(json); return newFlow; } /// <summary> /// 根据相同的标识重新组织 /// </summary> /// <returns></returns> public Workflow Clone() { var newFlow=Clone_Serialize(); foreach(var node in newFlow.Nodes) { node.Parent = newFlow; } foreach (var link in newFlow.Links) { if (link.Source != null) { var node = newFlow.Nodes.FirstOrDefault(n => n.Name == link.Source.Name); link.Source = node; } if (link.Target != null) { var node = newFlow.Nodes.FirstOrDefault(n => n.Name == link.Target.Name); link.Target = node; } } return newFlow; } public Workflow Clone_Memberwise() { return this.MemberwiseClone() as Workflow; } } }