设计模式篇-原型模式

介绍

原型模式的用意主要是复制对象,尤其当创建对象需要很大的开销时,使用原型模式最为合适。

案例

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
//定义原型接口
interface Prototype<T> extends Cloneable {
T clone() throws CloneNotSupportedException;
}
//原型具体类
class ConcretePrototype implements Prototype<ConcretePrototype> {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public ConcretePrototype clone() throws CloneNotSupportedException {
return (ConcretePrototype) super.clone();
}
}
class Test {
public static void main(String[] args) {
ConcretePrototype concrete = new ConcretePrototype();
Prototype concrete2 = concrete.clone();
}
}

已经创建的对象concrete,现在需要复制concrete,可以直接通过clone方法实现。

这样就是原型模式。

浅复制和深复制

然而,通过原生clone进行的复制是浅复制,即所有变量都是原对象的值,而所有对象的引用仍然指向原来的对象。

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
class Child{
private String name;
public Child(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Child{" +
"name='" + name + '\'' +
'}';
}
}
class ConcretePrototype implements Prototype<ConcretePrototype>, Serializable {
private Child child;
private String name;
public ConcretePrototype(String name, Child child) {
this.child = child;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Child getChild() {
return child;
}
public void setChild(Child child) {
this.child = child;
}
public ConcretePrototype clone() throws CloneNotSupportedException {
ConcretePrototype p = (ConcretePrototype) super.clone();
return p;
}
public static void main(String[] args) throws Exception {
ConcretePrototype prototype1 = new ConcretePrototype("father", new Child("son"));
System.out.println("original object:" + prototype1);
Prototype prototype2 = prototype1.clone();
System.out.println("cloned object:" + prototype2);
prototype1.setName("father changed");
prototype1.getChild().setName("son changed");
System.out.println("after change original object:" + prototype1);
System.out.println("after change cloned object:" + prototype2);
}
@Override
public String toString() {
return "ConcretePrototype{" +
"child=" + child +
", name='" + name + '\'' +
'}';
}
}

输出结果

1
2
3
4
original object:ConcretePrototype{child=Child{name='son'}, name='father'}
cloned object:ConcretePrototype{child=Child{name='son'}, name='father'}
after change original object:ConcretePrototype{child=Child{name='son changed'}, name='father changed'}
after change cloned object:ConcretePrototype{child=Child{name='son changed'}, name='father'}

可见,在对prototype1的child修改后,prototype2的child name 也发生了变化,说明俩个prototype持有同一个child引用。

那么如何进行深度复制

可以在prototype1 clone的基础上,继续克隆child。

1
2
3
4
5
public ConcretePrototype clone() throws CloneNotSupportedException {
ConcretePrototype p = (ConcretePrototype) super.clone();
p.child = this.child.clone();
return p;
}

也可以利用序列化,将对象写到流中,再读取出来完成深度复制。

案例改造

  • 首先注意ConcretePrototype和Child类要实现Serializable接口。
  • 之后ConcretePrototype中添加deepClone方法
1
2
3
4
5
6
7
8
9
10
11
//深复制
public ConcretePrototype deepClone() throws IOException, ClassNotFoundException {
// 写入当前对象的二进制流
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(this);
// 读出二进制流产生的新对象
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (ConcretePrototype) ois.readObject();
}

使用深复制

1
Prototype prototype2 = prototype1.deepClone();

输出结果

1
2
3
4
original object:ConcretePrototype{child=Child{name='son'}, name='father'}
cloned object:ConcretePrototype{child=Child{name='son'}, name='father'}
after change original object:ConcretePrototype{child=Child{name='son changed'}, name='father changed'}
after change cloned object:ConcretePrototype{child=Child{name='son'}, name='father'}

小结

什么是原型模式

定义对象为原型,通过克隆创建对象的实例副本。

UML
prototype_pattern

优点

  1. 可以隐藏复杂类的创建过程。
  2. 可以在运行时动态创建对象。
  3. 可以通过现有对象复制新对象。

缺点

  1. 结构复杂,几乎每个设计模式的通病。

适用场景

  1. 复制现有对象时。
  2. 需要从客户端隐藏对象的创建细节时。
  3. 运行时创建对象时。
  4. 对象创建需要巨大开销时。

实际应用

  1. java.lang.Object#clone()