Java 设计模式05——原型模式

模式定义

原型模式属于对象的创建模式。通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象。

模式结构

原型模式最主要的一点就是要求 原型对象 拥有克隆自己的能力,这样就可以调用其克隆方法来创建一个和原型对象类型相同的实例。原型模式主要有两种结构:简单形式和登记形式。

简单形式

简单形式中的三个角色:

  1. 客户角色,即提出创建对象请求的类;
  2. 抽象原型,即提供创建对象能力的角色,通常是接口或抽象类,通常可以使用Java提供的Cloneable接口,也可以自己实现;
  3. 具体原型,具体要创建出来的对象类型,实现(或覆盖)了抽象原型定义的接口方法。

具体代码示例:

抽象原型

1
2
3
public interface AbsPrototype {
Object clone();
}

具体原型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class RealPrototype implements AbsPrototype {
private String name = "real";

public String getName() {
return name;
}

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

@Override
public Object clone() {
return new RealPrototype();
}
}

客户角色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class SimpleClient {
private final AbsPrototype prototype;

public SimpleClient(AbsPrototype prototype) {
this.prototype = prototype;
}

public AbsPrototype makePrototype() {
return (AbsPrototype) prototype.clone();
}

public static void main(String[] args) {
RealPrototype realPrototype = new RealPrototype();

SimpleClient client = new SimpleClient(realPrototype);
RealPrototype copy = (RealPrototype) client.makePrototype();
copy.setName("copyOne");
System.out.println(realPrototype.getName());
System.out.println(copy.getName());
}
}

登记形式

大体上和简单形式相同,只不过多了一个管理类,用键值对来将 抽象原型对象 不同的实现保存起来,然后要创建不同类型的 具体原型对象,只要根据相应的来进行复制即可。

抽象原型对象的另一种实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class AnotherPrototype implements AbsPrototype {
private String name = "another";

public String getName() {
return name;
}

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

@Override
public Object clone() {
return new AnotherPrototype();
}
}

不同类型原型对象管理角色

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
public class PrototypeManager {

private static volatile PrototypeManager instance;

public static PrototypeManager getInstance() {
if (instance == null) {
synchronized (PrototypeManager.class) {
if (instance == null) {
instance = new PrototypeManager();
}
}
}
return instance;
}

private PrototypeManager() {
this.prototypeMap = new HashMap<>();
}

private final Map<String, AbsPrototype> prototypeMap;

public void setPrototype(String prototypeId, AbsPrototype prototype) {
prototypeMap.put(prototypeId, prototype);
}

public void removePrototype(String prototypeId) {
prototypeMap.remove(prototypeId);
}

public AbsPrototype getPrototype(String prototypeId) {
return prototypeMap.get(prototypeId);
}
}

客户端角色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class CheckoutClient {
public static void main(String[] args) {
AbsPrototype p1 = new RealPrototype();
PrototypeManager.getInstance().setPrototype("p1", p1);

AbsPrototype p2 = new AnotherPrototype();
PrototypeManager.getInstance().setPrototype("p2", p2);

// 得到 RealPrototype的对象
RealPrototype copyReal = (RealPrototype) PrototypeManager.getInstance().getPrototype("p1");
AnotherPrototype copyAnother = (AnotherPrototype) PrototypeManager.getInstance().getPrototype("p2");

/*
* 由此例可以看出原型这种几种管理的几个明显缺点:
* 1. 容易产生 NullPointerException,得到的基础原型对象可能会被移除而不复存在了
* 2. 容易产生 ClassCastException,得到的对象可能是抽象原型的另一种实现
*
* 所以要保证只有一种类型的具体对象,这样就可以避免可能发生的类型转换问题。
*/
}
}

改进版

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
public class PrototypeManager {
public static final String PROTOTYPE_REAL = "real";
public static final String PROTOTYPE_ANOTHER = "another";
private static volatile PrototypeManager instance;

public static PrototypeManager getInstance() {
if (instance == null) {
synchronized (PrototypeManager.class) {
if (instance == null) {
instance = new PrototypeManager();
}
}
}
return instance;
}

private PrototypeManager() {
this.prototypeMap = new HashMap<>();
}

private final Map<String, AbsPrototype> prototypeMap;

public void removePrototype(String prototypeId) {
prototypeMap.remove(prototypeId);
}

public AbsPrototype getPrototype(String prototypeId) {
AbsPrototype absPrototype = prototypeMap.get(prototypeId);
if (absPrototype == null) {
if (PROTOTYPE_REAL.equals(prototypeId)) {
absPrototype = new RealPrototype();
prototypeMap.put(PROTOTYPE_REAL, absPrototype);
} else if (PROTOTYPE_ANOTHER.equals(prototypeId)){
absPrototype = new AnotherPrototype();
prototypeMap.put(PROTOTYPE_ANOTHER, absPrototype);
} else {
throw new IllegalArgumentException("prototypeId:" + prototypeId + " not supported, please check it out!");
}
}
return absPrototype;
}
}

相应的CheckoutClient改为:

1
2
3
4
5
6
7
8
9
public class CheckoutClient {

public static void main(String[] args) {

// 得到 RealPrototype的对象
RealPrototype copyReal = (RealPrototype) PrototypeManager.getInstance().getPrototype(PrototypeManager.PROTOTYPE_REAL);
AnotherPrototype copyAnother = (AnotherPrototype) PrototypeManager.getInstance().getPrototype(PrototypeManager.PROTOTYPE_ANOTHER);
}
}

原型对象的创建都集中在PrototypeManager中,新增支持的原型对象只需要定义新增的原型对象,然后相应的添加到PrototypeManager中即可。

模式总结

  1. 应用场景,当需要大范围的或者平凡的创建某类对象时;
  2. 应用先决条件,原型对象必须支持克隆自己的方法;

延伸拓展

上面说了,原型模式实现的前提是原型对象必须支持克隆自己的方法。这里的克隆有两种形式:浅克隆和深克隆。

浅克隆与深克隆

浅克隆:只负责克隆按值传递的数据(比如基本数据类型、String类型),而不复制它所引用的对象,换言之,所有的对其他对象的引用都仍然指向原来的对象。

深克隆:除了浅度克隆要克隆的值外,还负责克隆引用类型的数据。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深度克隆把要复制的对象所引用的对象都复制了一遍,而这种对被引用到的对象的复制叫做间接复制。

深度克隆要深入到多少层,是一个不易确定的问题。在决定以深度克隆的方式复制一个对象的时候,必须决定对间接复制的对象时采取浅度克隆还是继续采用深度克隆。因此,在采取深度克隆时,需要决定多深才算深。此外,在深度克隆的过程中,很可能会出现循环引用的问题,必须小心处理。

一般的Object的clone()就是浅克隆,而Java中通常通过序列化来实现对象的深克隆,即先将对象序列化到流中,然后再从反序列化读出对象。对于不能被序列化的对象,需要用transient将其排除在克隆之外。