使程序运行更高效——原型模式
原型模式的定义
用原型实例指定创建的种类,并通过复制这些原型创建新的对象。原型模式的本质上就是对象拷贝。·
原型模式的使用场景
(1)类初始化需要消耗非常多的资源,这个资源包括数据、硬件资源等,通过原型复制这些消耗。
(2)通过new产生一个对象需要非常繁琐的数据准备或访问权限,这时可以使用原型模式。
(3)一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑用原型模式复制多个对象供调用者使用,这种方式叫保护性拷贝
原型模式的示例
具体的原型类WordDocument.class
/**
* description: Cloneable也是一个标识接口,表明这个类的对象是可拷贝的
*/
public class WordDocument implements Cloneable {
//文本
private String text;
//图片名列表
private ArrayList<String> images = new ArrayList<>();
public WordDocument() {
Log.i("WordDocument", "WordDocument的构造函数");
}
/**
* description: 这个方法并不是Cloneable接口中的,而是Object中的方法
* 如果没有实现Cloneable接口却调用了clone()函数会报异常
*/
@Override
protected WordDocument clone() {
try {
WordDocument doc = (WordDocument) super.clone();
doc.text = this.text;
doc.images = this.images;
return doc;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public ArrayList<String> getImages() {
return images;
}
public void addImages(String img) {
this.images.add(img);
}
public void showDocument() {
Log.i("WordDocument", "Text: " + text);
Log.i("WordDocument", "Images List: ");
for (String image : images) {
Log.i("WordDocument", "image name: " + image);
}
}
Client.class
public class Client {
public void Test() {
//构建文档对象
WordDocument originDoc = new WordDocument();
//编辑文档,添加图片等
originDoc.setText("这是一篇文档");
originDoc.addImages("图片1");
originDoc.addImages("图片2");
originDoc.addImages("图片3");
originDoc.showDocument();
//以原始文档为原型,拷贝一份副本
WordDocument doc2 = originDoc.clone();
doc2.showDocument();
//修改文档副本,不会影响原始文档
doc2.setText("这是修改过的Doc2文本");
doc2.showDocument();
//再次打印原始文档
originDoc.showDocument();
}
}
调用new Client().Test(),在log中查看日志,如图原型模式log1.jpg
由log可以看到,doc2是通过originDoc.clone()创建的,并且doc2第一次输出的时候和originDoc输出是一样的,而doc2修改了文本内容以后并不会影响originDoc的文本内容,保证了originDoc的安全性。
需要注意一个问题:通过clone拷贝对象时不会执行构造函数,在log中也能看得出来,所以不能在构造函数中执行代码。
浅拷贝和深拷贝
其实上面的代码实际上是一个浅拷贝,不是将原始文档的所有字段都重新构造了一份,而是副本字段引用原始文本字段。那这么说的话,我们都知道A引用B,那么A和B这两个对象指向同一个地址,A变,B也会跟着变。接下来做一个小小的变化:
Client.class
public class Client {
public void Test() {
WordDocument originDoc = new WordDocument();
originDoc.setText("这是一篇文档");
originDoc.addImages("图片1");
originDoc.addImages("图片2");
originDoc.addImages("图片3");
originDoc.showDocument();
WordDocument doc2 = originDoc.clone();
doc2.showDocument();
doc2.setText("这是修改过的Doc2文本");
//在doc2中新增一个图片 “ 哈哈.jpg ”
doc2.addImages("哈哈.jpg");
doc2.showDocument();
originDoc.showDocument();
}
}
调用之后再来看下打印的日志,请看原型模式log2.jpg
哦吼,originDoc中也新增了一个图片哈哈.jpg,这是为啥呢?这是因为WordDocument的clone()方法只是简单的进行浅拷贝,引用类型的新对象doc2的images只是单纯的指向了this.images引用,并没有重新构造一个images对象,这就导致doc2中的images与原始文档中的是同一个对象,修改其中一个文档,另一个文档也会改变,这不是我们想看到的,那么这时就需要采用深拷贝,即在拷贝对象时,对于引用型的字段也要采用拷贝的形式,修改WordDocument中的clone()方法:
@Override
protected WordDocument clone() {
try {
WordDocument doc = (WordDocument) super.clone();
doc.text = this.text;
//对image对象也调用clone()函数,进行深拷贝
doc.images = (ArrayList<String>) this.images.clone();
return doc;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
再来看看效果图原型模式log3.jpg
原型模式是一个非常简单的模式,核心就是对原始对象的拷贝,为了减少错误,建议尽量使用深拷贝。
小结
原型模式本质上就是对象拷贝,使用原型模式可以解决构建复杂对象的资源消耗问题,还有个用途是保护性拷贝,也就是某个对象对外可能是只读的,为了防止外部对这个对象修改,可以返回一个对象拷贝实现只读的限制。
优点
原型模式是在内存中二进制流的拷贝,要比直接new一个对象性能好很多,特别是要在一个循环体内产生大量的对象时。
缺点
直接内存中拷贝,所以构造函数不会执行。