意图

原型模式是一种创建型模式,使你能够复制已有对象,而又无需使代码依赖它们所属的类。

问题

如果你有一个对象,并希望生成与其完全相同的一个复制品,你该如何实现呢?首先,你必须新建一个属于相同类的对象。然后,你必须遍历原始对象的所有成员变量,并将成员变量值复制到新对象中。

不错! 但有个小问题。并非所有对象都能通过这种方式进行复制,因为有些对象可能拥有私有成员变量,它们在对象本身以外是不可见的。

 “从外部” 复制对象并非总是可行

直接复制还有另外一个问题。因为你必须知道对象所属的类才能创建复制品,所以代码必须依赖该类。即使你可以接受额外的依赖性,那还有另外一个问题:有时你只知道对象所实现的接口,而不知道其所属的具体类,比如可向方法的某个参数传入实现了某个接口的任何对象。

解决方案

原型模式将克隆过程委派给被克隆的实际对象。模式为所有支持克隆的对象声明了一个通用接口,该接口让你能够克隆对象,同时又无需将代码和对象所属类耦合。通常情况下,这样的接口中仅包含一个克隆方法。

所有的类对克隆方法的实现都非常相似。该方法会创建一个当前类的对象,然后将原始对象所有的成员变量值复制到新建的类中。你甚至可以复制私有成员变量,因为绝大部分编程语言都允许对象访问其同类对象的私有成员变量。

支持克隆的对象即为原型。当你的对象有几十个成员变量和几百种类型时,对其进行克隆甚至可以代替子类的构造。

预生成原型可以代替子类的构造

其运作方式如下:创建一系列不同类型的对象并不同的方式对其进行配置。如果所需对象与预先配置的对象相同,那么你只需克隆原型即可,无需新建一个对象。

结构

基本实现

  1. 原型(Prototype)接口将对克隆方法进行声明。在绝大多数情况下,其中只会有一个名为clone克隆的方法。
  2. 具体原型(Concrete Prototype)类将实现克隆方法。除了将原始对象的数据复制到克隆体中之外,该方法有时还需处理克隆过程中的极端情况,例如克隆关联对象和梳理递归依赖等等。
  3. 客户端(Client)可以复制实现了原型接口的任何对象。

原型注册表实现

  1. 原型注册表(Prototype Registry)提供了一种访问常用原型的简单方法,其中存储了一系列可供随时复制的预生成对象。最简单的注册表原型是一个名称 → 原型的哈希表。但如果需要使用名称以外的条件进行搜索,你可以创建更加完善的注册表版本。

实现方式

  1. 创建原型接口,并在其中声明克隆方法。如果你已有类层次结构,则只需在其所有类中添加该方法即可。
  2. 原型类必须另行定义一个以该类对象为参数的构造函数。构造函数必须复制参数对象中的所有成员变量值到新建实体中。如果你需要修改子类,则必须调用父类构造函数,让父类复制其私有成员变量值。
    如果编程语言不支持方法重载,那么你可能需要定义一个特殊方法来复制对象数据。在构造函数中进行此类处理比较方便,因为它在调用new运算符后会马上返回结果对象。
  3. 克隆方法通常只有一行代码:使用new运算符调用原型版本的构造函数。注意,每个类都必须显式重写克隆方法并使用自身类名调用new运算符。否则,克隆方法可能会生成父类的对象。
  4. 你还可以创建一个中心化原型注册表,用于存储常用原型。
    你可以新建一个工厂类来实现注册表,或者在原型基类中添加一个获取原型的静态方法。该方法必须能够根据客户端代码设定的条件进行搜索。搜索条件可以是简单的字符串,或者是一组复杂的搜索参数。找到合适的原型后,注册表应对原型进行克隆,并将复制生成的对象返回给客户端。
    最后还要将对子类构造函数的直接调用替换为对原型注册表工厂方法的调用。

代码演示

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
75
76
77
78
79
80
81
82
using System;

namespace RefactoringGuru.DesignPatterns.Prototype.Conceptual
{
public class Person
{
public int Age;
public DateTime BirthDate;
public string Name;
public IdInfo IdInfo;

public Person ShallowCopy()
{
return (Person) this.MemberwiseClone();
}

public Person DeepCopy()
{
Person clone = (Person) this.MemberwiseClone();
clone.IdInfo = new IdInfo(IdInfo.IdNumber);
clone.Name = String.Copy(Name);
return clone;
}
}

public class IdInfo
{
public int IdNumber;

public IdInfo(int idNumber)
{
this.IdNumber = idNumber;
}
}

class Program
{
static void Main(string[] args)
{
Person p1 = new Person();
p1.Age = 42;
p1.BirthDate = Convert.ToDateTime("1977-01-01");
p1.Name = "Jack Daniels";
p1.IdInfo = new IdInfo(666);

// Perform a shallow copy of p1 and assign it to p2.
Person p2 = p1.ShallowCopy();
// Make a deep copy of p1 and assign it to p3.
Person p3 = p1.DeepCopy();

// Display values of p1, p2 and p3.
Console.WriteLine("Original values of p1, p2, p3:");
Console.WriteLine(" p1 instance values: ");
DisplayValues(p1);
Console.WriteLine(" p2 instance values:");
DisplayValues(p2);
Console.WriteLine(" p3 instance values:");
DisplayValues(p3);

// Change the value of p1 properties and display the values of p1,
// p2 and p3.
p1.Age = 32;
p1.BirthDate = Convert.ToDateTime("1900-01-01");
p1.Name = "Frank";
p1.IdInfo.IdNumber = 7878;
Console.WriteLine("\nValues of p1, p2 and p3 after changes to p1:");
Console.WriteLine(" p1 instance values: ");
DisplayValues(p1);
Console.WriteLine(" p2 instance values (reference values have changed):");
DisplayValues(p2);
Console.WriteLine(" p3 instance values (everything was kept the same):");
DisplayValues(p3);
}

public static void DisplayValues(Person p)
{
Console.WriteLine(" Name: {0:s}, Age: {1:d}, BirthDate: {2:MM/dd/yy}",
p.Name, p.Age, p.BirthDate);
Console.WriteLine(" ID#: {0:d}", p.IdInfo.IdNumber);
}
}
}

执行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Original values of p1, p2, p3:
p1 instance values:
Name: Jack Daniels, Age: 42, BirthDate: 01/01/77
ID#: 666
p2 instance values:
Name: Jack Daniels, Age: 42, BirthDate: 01/01/77
ID#: 666
p3 instance values:
Name: Jack Daniels, Age: 42, BirthDate: 01/01/77
ID#: 666

Values of p1, p2 and p3 after changes to p1:
p1 instance values:
Name: Frank, Age: 32, BirthDate: 01/01/00
ID#: 7878
p2 instance values (reference values have changed):
Name: Jack Daniels, Age: 42, BirthDate: 01/01/77
ID#: 7878
p3 instance values (everything was kept the same):
Name: Jack Daniels, Age: 42, BirthDate: 01/01/77
ID#: 666

参考原文:原型设计模式