介绍
将一个“样板”对象“克隆”出一个内部属性一致的对象,被复制的对象称为“原型”。
使用场景
- 类初始化需要消耗非常多的资源,这个资源包括数据、硬件资源等,通过原型拷贝避免这些消耗;
- 通过 new 产生一个对象需要非常繁琐的数据准备或访问权限,这时可以使用原型模式;
- 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝。
三个对象
- Client:客户端用户;
- Prototype:抽象类或者接口,声明具备 clone 能力;
- ConcretePrototype:具体的原型类。
简单实现
说明:文档类 WordDocument ,需要拷贝一份再进行编辑。
- 浅拷贝
文档类型,扮演的是ConcretePrototype角色,而Cloneable是代表Prototype角色
public class WordDocument implements Cloneable {
//文本
private String mText;
//图片名列表
private ArrayList<String> mImages = new ArrayList<>();
private int num;
public WordDocument() {
System.out.println("--------------WordDocument构造函数-------------");
}
@Override
protected WordDocument clone() {
try {
WordDocument doc = (WordDocument) super.clone();
doc.mText = this.mText;
doc.mImages = this.mImages;
doc.num = this.num;
return doc;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
public String getText() {
return mText;
}
public void setText(String text) {
mText = text;
}
public ArrayList<String> getImages() {
return mImages;
}
public void addImages(String image) {
mImages.add(image);
}
public void showDocument() {
System.out.println("--------------Word Content Start---------------");
System.out.println("Text: " + mText);
System.out.println("num: " + num);
System.out.println("Images List: ");
for (String imageName : mImages) {
System.out.println("image name: " + imageName);
}
System.out.println("---------------Word Content End--------------");
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
}
客户端测试
public class Client {
public static void main(String[] args) {
//1.构建文档对象
WordDocument originDoc = new WordDocument();
//2.编辑文档,添加图片等
originDoc.setText("这是一篇文档");
originDoc.setNum(1);
originDoc.addImages("图片1");
originDoc.addImages("图片2");
originDoc.addImages("图片3");
originDoc.showDocument();
//以原始文档为原型,拷贝一份副本
WordDocument doc2 = originDoc.clone();
doc2.showDocument();
//修改文档副本,不会影响原始文档
doc2.setText("这是修改过的Doc2文本");
doc2.setNum(2);
doc2.addImages("haha.jpg");
doc2.showDocument();
System.out.println("doc2Text=" + doc2.getText().hashCode());
System.out.println("originDoc=" + originDoc.getText().hashCode());
originDoc.showDocument();
}
}
打印结果
--------------WordDocument构造函数-------------
--------------Word Content Start---------------
Text: 这是一篇文档
num: 1
Images List:
image name: 图片1
image name: 图片2
image name: 图片3
---------------Word Content End--------------
--------------Word Content Start---------------
Text: 这是一篇文档
num: 1
Images List:
image name: 图片1
image name: 图片2
image name: 图片3
---------------Word Content End--------------
--------------Word Content Start---------------
Text: 这是修改过的Doc2文本
num: 2
Images List:
image name: 图片1
image name: 图片2
image name: 图片3
image name: haha.jpg
---------------Word Content End--------------
doc2Text=1742990845
originDoc=1016230457
--------------Word Content Start---------------
Text: 这是一篇文档
num: 1
Images List:
image name: 图片1
image name: 图片2
image name: 图片3
image name: haha.jpg
---------------Word Content End--------------
ConcretePrototype --> WordDocument 类;
Prototype --> Cloneable 接口。
通过 clone 对象不会执行构造函数。
问题:修改靠背后 mImages 会出现原始数据也发生改变的情况。
- 深拷贝
示例与上面类似
WordDocument 类中
@Override
protected WordDocument clone() {
try {
WordDocument doc = (WordDocument) super.clone();
doc.mText = this.mText;
// doc.mImages = this.mImages;
doc.mImages = (ArrayList<String>) this.mImages.clone();
doc.num = this.num;
return doc;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
打印结果
--------------WordDocument构造函数-------------
--------------Word Content Start---------------
Text: 这是一篇文档
num: 1
Images List:
image name: 图片1
image name: 图片2
image name: 图片3
---------------Word Content End--------------
--------------Word Content Start---------------
Text: 这是一篇文档
num: 1
Images List:
image name: 图片1
image name: 图片2
image name: 图片3
---------------Word Content End--------------
--------------Word Content Start---------------
Text: 这是修改过的Doc2文本
num: 2
Images List:
image name: 图片1
image name: 图片2
image name: 图片3
image name: haha.jpg
---------------Word Content End--------------
doc2Text=1742990845
originDoc=1016230457
--------------Word Content Start---------------
Text: 这是一篇文档
num: 1
Images List:
image name: 图片1
image name: 图片2
image name: 图片3
---------------Word Content End--------------
解决浅拷贝出现的问题,实现拷贝对象与原对象互不干扰。
原理:浅拷贝实际上是将副本文档中字段指向原始文档字段。即指向同一地址。
深拷贝的方式就是在拷贝对象时,对于引用型的字段也采用拷贝的方式,而不是单纯引用的形式。
这样可以避免操作副本时,影响到原始对象。
Android 源码中的原型模式
Intent 的使用。Java 中的 ArrayList。
总结
-
优点
原型模式是在内存中二进制流的拷贝,要比直接 new 一个对象性能好很多,特别是要在一个循环体内产生大量对象时,原型模式可以更好地体现其优点。
-
缺点
直接在内存中拷贝,构造函数不会执行,需要注意这个问题。优点是减少了约束,缺点也是减少了约束,实际使用时需要考虑清楚。