再读斋

Android设计模式之(三)原型模式

介绍

原型模式是一种创建型的模式。原型表示该模式有一个样板实例,用户从这个样板对象中复制出一个内部属性一致的对象,被复制的实例就称为原型。原型模式多用于创复杂的或者构造耗时的实例,因为这种情况下,复制一个已经存在的实例可以使程序运行更高效。

定义

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

使用场景

  1. 类初始化需要消耗非常多的资源,包括数据、硬件资源等,通过原型拷贝避免这些消耗。
  2. 通过new产生一个对象需要非常繁琐的数据准备或访问权限,这时可以使用原型模式。
  3. 一个对象需要提供给其他对象访问,而且各个调用者可能需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝。

需要注意的是,通过实现Cloneable接口的原型模式在调用clone函数构造实例时并不一定比通过new操作速度快,只有当通过new构造对象较为耗时或者资源消耗比较大时,通过clone方法才能获得效率上的提升。因此,使用Cloneable时需要考虑构建对象的成本以及做一些效率上的测试。当然,实现原型模式也并不一定非要实现Cloneable接口,也有其他的实现方式。

UML类图

原型模式的UML类图如下:

原型模式的UML类图

角色介绍:

  • Client:客户端用户
  • Prototype:抽象类或者接口,声明具clone的能力
  • ConcretePrototype:具体的原型类

实现

下面以简单的文档拷贝为例来演示一下简单的原型模式,例子中首先创建一个文档对象,即WordDocument,这个文档包括文字和图片。用户经过产时间的内容编辑后,打开该文档做进一步的编辑,但是这个文档编辑后是否被采用还不确定,因此为了安全起见,用户需要将当前文档拷贝一份,然后再在这个文档副本上进行修改。如下所示

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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package com.lgq.pattern.prototype;
import java.util.ArrayList;
/**
* 文档类型,扮演的是ConcretePrototype角色,而Cloneable是代表Prototype角色
* @author liuguoquan
*
*/
public class WordDocument implements Cloneable{
//文本
private String text;
//图片名列表
private ArrayList<String> images = new ArrayList<>();
public WordDocument() {
// TODO Auto-generated constructor stub
System.out.println("---WordDocument构造函数---");
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public ArrayList<String> getImages() {
return images;
}
public void addImage(String image) {
images.add(image);
}
@Override
protected WordDocument clone() {
// TODO Auto-generated method stub
try {
WordDocument document = (WordDocument) super.clone();
document.text = this.text;
//浅拷贝
document.images = this.images;
//深拷贝
// document.images = (ArrayList<String>) this.images.clone();
return document;
} catch (Exception e) {
// TODO: handle exception
}
return null;
}
@Override
public String toString() {
return "WordDocument [text=" + text + ", images=" + images + "]";
}
}
package com.lgq.pattern.prototype;
public class Client {
public static void main(String[] args) {
WordDocument document = new WordDocument();
document.setText("这是一篇文档");
document.addImage("image1");
document.addImage("image2");
System.out.println(document.toString());
//拷贝一份副本
WordDocument document2 = document.clone();
System.out.println(document2.toString());
//修改文档副本,不会影响原始文档
document2.setText("这是修改过的document2文档");
System.out.println(document2.toString());
System.out.println(document.toString());
}
}
打印结果:
---WordDocument构造函数---
WordDocument [text=这是一篇文档, images=[image1, image2]]
WordDocument [text=这是一篇文档, images=[image1, image2]]
WordDocument [text=这是修改过的document2文档, images=[image1, image2]]
WordDocument [text=这是一篇文档, images=[image1, image2]]

需要注意的是,上述的例子中WordDocument的构造函数只运行了一次,通过clone拷贝对象时并不会执行构造函数!

总结

原型模式本质上是对象拷贝,容易出现的问题是深拷贝、浅拷贝。使用原型模式可以解决构建复杂对象的资源消耗问题,能够在某些场景下提升创建对象的效率。还有一个重要用途就是保护性拷贝,也就是某个对象对外可能是只读的,为了防止外部对这个只读对象修改,通过可以通过返回一个对象拷贝的形式实现只读的限制。

优点

原型模式是在内存中二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。

缺点

直接在内存中拷贝时,构造函数是不会执行的,在实际开发中应该注意这个潜在的问题。

刘涤生 wechat