C#-设计模式实践——使用序列化实现原型模式

原型模式是一种经常使用的创建型设计模式,简单地说就是通过克隆(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;
}
}
}