原型模式(Prototype),用原型实例指定创建对象的种类,并且通过拷贝这些原型,创建新的对象。
原型模式是一种创建型设计模式,其实就是从一个对象再创建另外一个可定制的对象,而且不需要知道任何创建的细节。
示例
有一个简历类,必须要有姓名,可以设置性别和年龄,也可以设置工作经历。最后复制三份相同简历。
1 | /** |
2 | * Resume |
3 | */ |
4 | public class Resume { |
5 | |
6 | private String name; |
7 | |
8 | private String sex; |
9 | |
10 | private String age; |
11 | |
12 | private String timeArea; |
13 | |
14 | private String company; |
15 | |
16 | public Resume(String name) { |
17 | this.name = name; |
18 | } |
19 | |
20 | public void setPersonInfo(String sex, String age) { |
21 | this.sex = sex; |
22 | this.age = age; |
23 | } |
24 | |
25 | public void setWorkExperience(String timeArea, String company) { |
26 | this.timeArea = timeArea; |
27 | this.company = company; |
28 | } |
29 | |
30 | |
31 | public String toString() { |
32 | return "Resume{" + |
33 | "name='" + name + '\'' + |
34 | ", sex='" + sex + '\'' + |
35 | ", age='" + age + '\'' + |
36 | ", timeArea='" + timeArea + '\'' + |
37 | ", company='" + company + '\'' + |
38 | '}'; |
39 | } |
40 | } |
41 | |
42 | /** |
43 | * Main |
44 | */ |
45 | public class Main { |
46 | |
47 | public static void main(String[] args) { |
48 | Resume a = new Resume("God.Gao"); |
49 | a.setPersonInfo("woman", "20"); |
50 | a.setWorkExperience("2014-2018", "XXXCompany"); |
51 | |
52 | Resume b = new Resume("God.Gao"); |
53 | b.setPersonInfo("woman", "20"); |
54 | b.setWorkExperience("2014-2018", "XXXCompany"); |
55 | |
56 | Resume c = new Resume("God.Gao"); |
57 | c.setPersonInfo("woman", "20"); |
58 | c.setWorkExperience("2014-2018", "XXXCompany"); |
59 | |
60 | System.out.println(a.toString()); |
61 | System.out.println(b.toString()); |
62 | System.out.println(c.toString()); |
63 | } |
64 | } |
此写法比较好理解,简单易操作。但是,假如需要二十份相同简历,就需要二十次实例化,并且每 new 一次,都需要执行一次构造函数,如果构造函数的执行时间很长,那么多次的执行这个初始化操作就实在是太低效了。假如我们又需要修改其中一个属性的值,那就要修改二十次,这样就很麻烦,效率也低。
使用原型模式改进
Java 中 Object 类是所有类的基类,Object 类提供了一个 clone() 方法,该方法可以将一个 Java 对象复制一份,但是需要实现 clone() 的 Java 类必须要实现一个 Cloneable 接口,这样就可以完成原型模式了。
1 | /** |
2 | * Resume |
3 | */ |
4 | public class Resume implements Cloneable { |
5 | |
6 | private String name; |
7 | |
8 | private String sex; |
9 | |
10 | private String age; |
11 | |
12 | private String timeArea; |
13 | |
14 | private String company; |
15 | |
16 | public Resume(String name) { |
17 | this.name = name; |
18 | } |
19 | |
20 | public void setPersonInfo(String sex, String age) { |
21 | this.sex = sex; |
22 | this.age = age; |
23 | } |
24 | |
25 | public void setWorkExperience(String timeArea, String company) { |
26 | this.timeArea = timeArea; |
27 | this.company = company; |
28 | } |
29 | |
30 | |
31 | protected Object clone() throws CloneNotSupportedException { |
32 | return super.clone(); |
33 | } |
34 | |
35 | |
36 | public String toString() { |
37 | return "Resume{" + |
38 | "name='" + name + '\'' + |
39 | ", sex='" + sex + '\'' + |
40 | ", age='" + age + '\'' + |
41 | ", timeArea='" + timeArea + '\'' + |
42 | ", company='" + company + '\'' + |
43 | '}'; |
44 | } |
45 | } |
46 | |
47 | /** |
48 | * Main |
49 | */ |
50 | public class Main { |
51 | |
52 | public static void main(String[] args) throws CloneNotSupportedException { |
53 | Resume a = new Resume("God.Gao"); |
54 | a.setPersonInfo("woman", "20"); |
55 | a.setWorkExperience("2014-2018", "XXXCompany"); |
56 | |
57 | Resume b = (Resume) a.clone(); |
58 | |
59 | Resume c = (Resume) a.clone(); |
60 | |
61 | System.out.println(a.toString()); |
62 | System.out.println(b.toString()); |
63 | System.out.println(c.toString()); |
64 | } |
65 | } |
原型模式实际就是对象的克隆。一般在初始化的信息不发生变化的情况下,克隆是最好的办法。这既隐藏了对象创建的细节,又对性能是大大的提高。
上述代码实现的克隆属于浅克隆。既然有浅克隆,就有深克隆。
浅克隆 VS 深克隆
浅克隆
浅克隆是指,被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。——《大话设计模式》
对于数据类型是基本数据类型的成员变量,浅克隆会直接进行值传递,也就是将该属性值复制一份给新的对象。
对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅克隆会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象;因此原始对象及其复本引用同一对象。在这种情况下,在一个对象中修改该成员变量的值会影响到另一个对象的该成员变量的值。
深克隆
深克隆,顾名思义,除了复制对象的所有基本数据类型的成员变量值,还为所有引用数据类型的成员变量申请存储空间,并复制每个引用数据类型成员变量所引用的对象,直到该对象可达的所有对象。也就是说,对象进行深克隆要对整个对象(包括对象的引用类型)进行复制。这样就不会出现一个对象的成员变量的值被修改会影响另一个对象的成员变量的值。
浅克隆&深克隆示例对比
示例:复制下面的简历(Resume)类。
1 | public class Resume { |
2 | |
3 | private String name; |
4 | |
5 | private String sex; |
6 | |
7 | private String age; |
8 | |
9 | private WorkExperience workExperience; |
10 | |
11 | public Resume(String name) { |
12 | this.name = name; |
13 | this.workExperience = new WorkExperience(); |
14 | } |
15 | |
16 | public void setPersonInfo(String sex, String age) { |
17 | this.sex = sex; |
18 | this.age = age; |
19 | } |
20 | |
21 | public void setWorkExperience(String workDate, String company) { |
22 | this.workExperience.setWorkDate(workDate); |
23 | this.workExperience.setCompany(company); |
24 | } |
25 | |
26 | |
27 | public String toString() { |
28 | return "Resume{" + |
29 | "name='" + name + '\'' + |
30 | ", sex='" + sex + '\'' + |
31 | ", age='" + age + '\'' + |
32 | ", workExperience=" + workExperience + |
33 | '}'; |
34 | } |
35 | } |
36 | |
37 | /** |
38 | * WorkExperience |
39 | */ |
40 | public class WorkExperience { |
41 | |
42 | private String workDate; |
43 | |
44 | private String company; |
45 | |
46 | public String getWorkDate() { |
47 | return workDate; |
48 | } |
49 | |
50 | public void setWorkDate(String workDate) { |
51 | this.workDate = workDate; |
52 | } |
53 | |
54 | public String getCompany() { |
55 | return company; |
56 | } |
57 | |
58 | public void setCompany(String company) { |
59 | this.company = company; |
60 | } |
61 | |
62 | |
63 | public String toString() { |
64 | return "WorkExperience{" + |
65 | "workDate='" + workDate + '\'' + |
66 | ", company='" + company + '\'' + |
67 | '}'; |
68 | } |
69 | } |
- 使用浅克隆复制
1 | /** |
2 | * Resume |
3 | */ |
4 | public class Resume implements Cloneable { |
5 | |
6 | private String name; |
7 | |
8 | private String sex; |
9 | |
10 | private String age; |
11 | |
12 | private WorkExperience workExperience; |
13 | |
14 | public Resume(String name) { |
15 | this.name = name; |
16 | this.workExperience = new WorkExperience(); |
17 | } |
18 | |
19 | public void setPersonInfo(String sex, String age) { |
20 | this.sex = sex; |
21 | this.age = age; |
22 | } |
23 | |
24 | public void setWorkExperience(String workDate, String company) { |
25 | this.workExperience.setWorkDate(workDate); |
26 | this.workExperience.setCompany(company); |
27 | } |
28 | |
29 | |
30 | protected Object clone() throws CloneNotSupportedException { |
31 | return super.clone(); |
32 | } |
33 | |
34 | |
35 | public String toString() { |
36 | return "Resume{" + |
37 | "name='" + name + '\'' + |
38 | ", sex='" + sex + '\'' + |
39 | ", age='" + age + '\'' + |
40 | ", workExperience=" + workExperience + |
41 | '}'; |
42 | } |
43 | } |
44 | |
45 | /** |
46 | * WorkExperience |
47 | */ |
48 | public class WorkExperience implements Cloneable { |
49 | |
50 | private String workDate; |
51 | |
52 | private String company; |
53 | |
54 | public String getWorkDate() { |
55 | return workDate; |
56 | } |
57 | |
58 | public void setWorkDate(String workDate) { |
59 | this.workDate = workDate; |
60 | } |
61 | |
62 | public String getCompany() { |
63 | return company; |
64 | } |
65 | |
66 | public void setCompany(String company) { |
67 | this.company = company; |
68 | } |
69 | |
70 | |
71 | protected Object clone() throws CloneNotSupportedException { |
72 | return super.clone(); |
73 | } |
74 | |
75 | |
76 | public String toString() { |
77 | return "WorkExperience{" + |
78 | "workDate='" + workDate + '\'' + |
79 | ", company='" + company + '\'' + |
80 | '}'; |
81 | } |
82 | } |
83 | |
84 | /** |
85 | * Main |
86 | */ |
87 | public class Main { |
88 | |
89 | public static void main(String[] args) throws CloneNotSupportedException { |
90 | Resume a = new Resume("God.Gao"); |
91 | a.setPersonInfo("woman", "20"); |
92 | a.setWorkExperience("2014-2018", "XXXCompany"); |
93 | |
94 | Resume b = (Resume) a.clone(); |
95 | b.setWorkExperience("2015-2019", "xiaokejiCompany"); |
96 | |
97 | Resume c = (Resume) a.clone(); |
98 | c.setWorkExperience("2014-2017", "yunCompany"); |
99 | |
100 | System.out.println(a.toString()); |
101 | System.out.println(b.toString()); |
102 | System.out.println(c.toString()); |
103 | } |
104 | } |
结果:
1 | Resume{name='God.Gao', sex='woman', age='20', workExperience=WorkExperience{workDate='2014-2017', company='yunCompany'}} |
2 | Resume{name='God.Gao', sex='woman', age='20', workExperience=WorkExperience{workDate='2014-2017', company='yunCompany'}} |
3 | Resume{name='God.Gao', sex='woman', age='20', workExperience=WorkExperience{workDate='2014-2017', company='yunCompany'}} |
从结果可以看出,使用浅克隆复制对象,当对象中存在引用类型的成员变量时,只是复制了该成员变量的引用地址,并没有让该成员变量指向复制过的新对象。因此造成了一个对象修改该成员变量的值影响到了其他对象的该成员变量的值。
- 使用深克隆复制
深克隆有两种实现方式:1. 重写 clone() 方法,2. 使用序列化的方式。
- 重写 clone() 方法
1 | /** |
2 | * Resume |
3 | */ |
4 | public class Resume implements Cloneable { |
5 | |
6 | private String name; |
7 | |
8 | private String sex; |
9 | |
10 | private String age; |
11 | |
12 | private WorkExperience workExperience; |
13 | |
14 | public Resume(String name) { |
15 | this.name = name; |
16 | this.workExperience = new WorkExperience(); |
17 | } |
18 | |
19 | public void setPersonInfo(String sex, String age) { |
20 | this.sex = sex; |
21 | this.age = age; |
22 | } |
23 | |
24 | public void setWorkExperience(String workDate, String company) { |
25 | this.workExperience.setWorkDate(workDate); |
26 | this.workExperience.setCompany(company); |
27 | } |
28 | |
29 | |
30 | protected Object clone() throws CloneNotSupportedException { |
31 | Resume resume = (Resume) super.clone(); |
32 | // 对引用类型的属性进行克隆 |
33 | resume.workExperience = (WorkExperience) this.workExperience.clone(); |
34 | return resume; |
35 | } |
36 | |
37 | |
38 | public String toString() { |
39 | return "Resume{" + |
40 | "name='" + name + '\'' + |
41 | ", sex='" + sex + '\'' + |
42 | ", age='" + age + '\'' + |
43 | ", workExperience=" + workExperience + |
44 | '}'; |
45 | } |
46 | } |
47 | |
48 | |
49 | /** |
50 | * WorkExperience |
51 | */ |
52 | public class WorkExperience implements Cloneable { |
53 | |
54 | private String workDate; |
55 | |
56 | private String company; |
57 | |
58 | public String getWorkDate() { |
59 | return workDate; |
60 | } |
61 | |
62 | public void setWorkDate(String workDate) { |
63 | this.workDate = workDate; |
64 | } |
65 | |
66 | public String getCompany() { |
67 | return company; |
68 | } |
69 | |
70 | public void setCompany(String company) { |
71 | this.company = company; |
72 | } |
73 | |
74 | |
75 | protected Object clone() throws CloneNotSupportedException { |
76 | return super.clone(); |
77 | } |
78 | |
79 | |
80 | public String toString() { |
81 | return "WorkExperience{" + |
82 | "workDate='" + workDate + '\'' + |
83 | ", company='" + company + '\'' + |
84 | '}'; |
85 | } |
86 | } |
87 | |
88 | /** |
89 | * Main |
90 | */ |
91 | public class Main { |
92 | |
93 | public static void main(String[] args) throws CloneNotSupportedException { |
94 | Resume a = new Resume("God.Gao"); |
95 | a.setPersonInfo("woman", "20"); |
96 | a.setWorkExperience("2014-2018", "XXXCompany"); |
97 | |
98 | Resume b = (Resume) a.clone(); |
99 | b.setWorkExperience("2015-2019", "xiaokejiCompany"); |
100 | |
101 | Resume c = (Resume) a.clone(); |
102 | c.setWorkExperience("2014-2017", "yunCompany"); |
103 | |
104 | System.out.println(a.toString()); |
105 | System.out.println(b.toString()); |
106 | System.out.println(c.toString()); |
107 | } |
108 | } |
- 使用序列化的方式
注意:使用序列化方式实现深克隆时,所涉及的类必须实现 Serializable 接口。
1 | /** |
2 | * Resume |
3 | */ |
4 | public class Resume implements Serializable { |
5 | |
6 | private static final long serialVersionUID = 6395782880219068537L; |
7 | |
8 | private String name; |
9 | |
10 | private String sex; |
11 | |
12 | private String age; |
13 | |
14 | private WorkExperience workExperience; |
15 | |
16 | public Resume(String name) { |
17 | this.name = name; |
18 | this.workExperience = new WorkExperience(); |
19 | } |
20 | |
21 | public void setPersonInfo(String sex, String age) { |
22 | this.sex = sex; |
23 | this.age = age; |
24 | } |
25 | |
26 | public void setWorkExperience(String workDate, String company) { |
27 | this.workExperience.setWorkDate(workDate); |
28 | this.workExperience.setCompany(company); |
29 | } |
30 | |
31 | public Object deepClone() { |
32 | ByteArrayOutputStream byteArrayOutputStream = null; |
33 | ObjectOutputStream objectOutputStream = null; |
34 | ByteArrayInputStream byteArrayInputStream = null; |
35 | ObjectInputStream objectInputStream = null; |
36 | |
37 | try { |
38 | // 创建对象输出流 |
39 | byteArrayOutputStream = new ByteArrayOutputStream(); |
40 | objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); |
41 | // 将对象以对象流的方式输出 |
42 | objectOutputStream.writeObject(this); |
43 | |
44 | // 创建对象输入流 |
45 | byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); |
46 | objectInputStream = new ObjectInputStream(byteArrayInputStream); |
47 | // 读取对象流 |
48 | return objectInputStream.readObject(); |
49 | } catch (IOException | SecurityException | ClassNotFoundException e) { |
50 | return null; |
51 | } finally { |
52 | try { |
53 | if (objectInputStream != null) { |
54 | objectInputStream.close(); |
55 | } |
56 | if (byteArrayInputStream != null) { |
57 | byteArrayInputStream.close(); |
58 | } |
59 | if (objectOutputStream != null) { |
60 | objectOutputStream.close(); |
61 | } |
62 | if (byteArrayOutputStream != null) { |
63 | byteArrayOutputStream.close(); |
64 | } |
65 | } catch (IOException e) { |
66 | e.printStackTrace(); |
67 | } |
68 | } |
69 | } |
70 | |
71 | |
72 | public String toString() { |
73 | return "Resume{" + |
74 | "name='" + name + '\'' + |
75 | ", sex='" + sex + '\'' + |
76 | ", age='" + age + '\'' + |
77 | ", workExperience=" + workExperience + |
78 | '}'; |
79 | } |
80 | } |
81 | |
82 | /** |
83 | * WorkExperience |
84 | */ |
85 | public class WorkExperience implements Serializable { |
86 | |
87 | private static final long serialVersionUID = -939371138425810409L; |
88 | |
89 | private String workDate; |
90 | |
91 | private String company; |
92 | |
93 | public String getWorkDate() { |
94 | return workDate; |
95 | } |
96 | |
97 | public void setWorkDate(String workDate) { |
98 | this.workDate = workDate; |
99 | } |
100 | |
101 | public String getCompany() { |
102 | return company; |
103 | } |
104 | |
105 | public void setCompany(String company) { |
106 | this.company = company; |
107 | } |
108 | |
109 | |
110 | public String toString() { |
111 | return "WorkExperience{" + |
112 | "workDate='" + workDate + '\'' + |
113 | ", company='" + company + '\'' + |
114 | '}'; |
115 | } |
116 | } |
117 | |
118 | |
119 | /** |
120 | * Main |
121 | */ |
122 | public class Main { |
123 | |
124 | public static void main(String[] args) { |
125 | Resume a = new Resume("God.Gao"); |
126 | a.setPersonInfo("woman", "20"); |
127 | a.setWorkExperience("2014-2018", "XXXCompany"); |
128 | |
129 | Resume b = (Resume) a.deepClone(); |
130 | b.setWorkExperience("2015-2019", "xiaokejiCompany"); |
131 | |
132 | Resume c = (Resume) a.deepClone(); |
133 | c.setWorkExperience("2014-2017", "yunCompany"); |
134 | |
135 | System.out.println(a.toString()); |
136 | System.out.println(b.toString()); |
137 | System.out.println(c.toString()); |
138 | } |
139 | } |
结果:
1 | Resume{name='God.Gao', sex='woman', age='20', workExperience=WorkExperience{workDate='2014-2018', company='XXXCompany'}} |
2 | Resume{name='God.Gao', sex='woman', age='20', workExperience=WorkExperience{workDate='2015-2019', company='xiaokejiCompany'}} |
3 | Resume{name='God.Gao', sex='woman', age='20', workExperience=WorkExperience{workDate='2014-2017', company='yunCompany'}} |
小结
在创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率,不用重新初始化对象,而是动态地获得对象运行时的状态。如果原始对象发生变化(增加或者减少属性),其它克隆对象的也会发生相应的变化,无需修改代码。
在实现深克隆的时候可能需要比较复杂的代码,需要为每一个类实现一个克隆方法,这对全新的类来说不是很难,但对已有的类进行改造时,需要修改其源代码,违背了开放封闭原则。