Earth Guardian

You are not LATE!You are not EARLY!

0%

设计模式--创建型:原型模式

原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新对象

原型模式 Prototype

所谓原型模式 Prototype['protəˌtaɪp],就是指 clone 技术,以某个对象为原型,复制出一个新的对象。

类图结构

0050-Prototype-uml-classdiag.png

结构解析

  • Prototype
    抽象类,原型类申明一个 clone 自身的接口。
  • ConcretePrototype
    实现类,实现一个克隆自身的操作。
  • client
    客户端持有原型类实例。

Java 中的原型模式

Cloneable 接口

Java 中,原型模式的 Prototype 抽象类并不需要我们定义,Cloneable 接口实现了这个功能。先看看这个接口的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* A class implements the <code>Cloneable</code> interface to
* indicate to the {@link java.lang.Object#clone()} method that it
* is legal for that method to make a
* field-for-field copy of instances of that class.
* <p>
* Invoking Object's clone method on an instance that does not implement the
* <code>Cloneable</code> interface results in the exception
* <code>CloneNotSupportedException</code> being thrown.
* <p>
*/
public interface Cloneable {
}

源码中 Cloneable 是一个空接口,仅用来标记对象,并没有定义 clone 方法。但是 Java 中所有对象都继承了 Object 类,而 clone() 正是它的标准方法之一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Object {
...
protected Object clone() throws CloneNotSupportedException {
if (!(this instanceof Cloneable)) {
throw new CloneNotSupportedException("Class "+getClass().getName()+
"doesn't implement Cloneable");
}

return internalClone();
}

/*
* Native helper method for cloning.
*/
private native Object internalClone();
...
}
  • 实现 Cloneable 接口
    从源码中可以看出,如果要使用 Object.clone 则必须实现 Cloneable 接口,否则会抛出 CloneNotSupportedException 异常。
  • clone 新对象时不会调用构造函数
    clone 方法最终调用了 nativeinternalClone 方法,而这个方法是直接从堆内存中以二进制流拷贝的,所以产生新对象时并不会调用类的构造方法。

深/浅拷贝

深/浅拷贝主要区别在对类中引用对象的处理:

  • 浅拷贝 shallow copy
    浅拷贝在拷贝对象时,并不会拷贝对象里的引用对象,也就是说不拷贝该对象的属性。
  • 深拷贝 deep copy
    深拷贝在拷贝对象时,需要手动代码实现对引用对象的拷贝,也就是把该对象的所有属性也克隆出一份新的。

示例

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
83
84
85
86
87
88
89
90
91
92
93
94
// 1. Prototype
// It's Cloneable in Java.

// 2. ConcretePrototype
public class ConcretePrototypeShallowCopy implements Cloneable{
private String type;
private TestObject testObject;

public ConcretePrototypeShallowCopy(String type, TestObject testObject) {
this.type = type;
this.testObject = testObject;
}

@Override
protected ConcretePrototypeShallowCopy clone()
throws CloneNotSupportedException {
return (ConcretePrototypeShallowCopy)super.clone();
}

public void show(){
System.out.println(type + ": " + testObject.getName());
}
}

public class ConcretePrototypeDeepCopy implements Cloneable {
private String type;
private TestObject testObject;

public ConcretePrototypeDeepCopy(String type, TestObject testObject) {
this.type = type;
this.testObject = testObject;
}

@Override
protected ConcretePrototypeDeepCopy clone()
throws CloneNotSupportedException {
ConcretePrototypeDeepCopy deepCopy =
(ConcretePrototypeDeepCopy)super.clone();
deepCopy.testObject = new TestObject(testObject.getName());
return deepCopy;
}

public void show(){
System.out.println(type + ": " + testObject.getName());
}
}

// 3. Test
public class TestObject {
private String name;

public TestObject(String name) {
this.name = name;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

public class TestPrototype {
public static void main(String[] args) throws Exception{
TestObject testObject = new TestObject("TestObject");
ConcretePrototypeShallowCopy shallowCopy =
new ConcretePrototypeShallowCopy("ShallowCopy", testObject);
ConcretePrototypeShallowCopy shallowCopyClone = shallowCopy.clone();
shallowCopy.show();
shallowCopyClone.show();

ConcretePrototypeDeepCopy deepCopy =
new ConcretePrototypeDeepCopy("DeepCopy", testObject);
ConcretePrototypeDeepCopy deepCopyClone = deepCopy.clone();
deepCopy.show();
deepCopyClone.show();

testObject.setName("newTestObject");
System.out.println("####TestObject change name.####");
shallowCopyClone.show();
deepCopyClone.show();
}
}

// 4. Result
ShallowCopy: TestObject
ShallowCopy: TestObject
DeepCopy: TestObject
DeepCopy: TestObject
####TestObject change name.####
ShallowCopy: newTestObject
DeepCopy: TestObject

在测试用例中,修改 TestObject 对象的值后,两种拷贝表现出来的结果不一样:

  • 浅拷贝
    因为引用对象并没有克隆,指向的是同一个地址空间,所以 TestObject 对象修改后,浅拷贝生成的新对象会跟着变化。
  • 深拷贝
    深拷贝克隆了所有的引用对象,也就是在新地址中拷贝了引用对象的值,源 TestObject 对象的修改不会对深拷贝产生影响。

总结

原型模式适合在什么场景使用?

  • 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等
  • 初始化时需要非常繁琐的数据准备或访问权限,则可以使用原型模式
  • 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用

在实际项目中,原型模式通常和工厂方法模式一起出现,通过 clone 的方法创建一个对象,然后由工厂方法提供给调用者。

参考文档