模式介紹
原型模式是一種創(chuàng)建式模式。
用戶可以從一個樣板對象中復制出一個內(nèi)部屬性一致的對象,這個過程也就是我們常說的“克隆”。
被復制出的對象就是我們所說的“原型”,這個“原型”是可以進行定制的。
原型模式多用于創(chuàng)建復雜、構造耗時的對象,因為在這種情況下,利用原型模式復制一個已經(jīng)存在的對象可以使程序運行更高效。
應用場景
- 一個對象初始化時需要消耗非常多的資源;
- 一個對象需要提供給多個對象訪問,并且多個對象調(diào)用時需要進行定制。
需要注意的是,復制操作并不一定比new快,只有當new對象較為耗時或成本較高時,通過復制才能獲得效率上的提升。
因此,在使用原型模式時需要對對象的構建成本進行一些效率測試。
簡單實例
如何實現(xiàn)復制操作?
非常簡單,Java提供了一個叫做Cloneable的接口,我們只需實現(xiàn)該接口,重寫它的clone()方法即可。
現(xiàn)在我們來舉個簡單實例來演示原型模式:
假設我們現(xiàn)在有一篇文檔對象:
public class WordDocument{
//文本
private String text;
//作者
private String author;
//圖片集合
private ArrayList<String> imageList;
//....省略get、set
}
里面包含了作者、文本以及圖片合集,現(xiàn)在我們要讓這個文檔實現(xiàn)復制功能:
public class WordDocument implements Cloneable {
//文本
private String text;
//作者
private String author;
//圖片集合
private ArrayList<String> imageList;
@Override
protected WordDocument clone() {
WordDocument document = null;
try {
document = (WordDocument) super.clone();
document.text = this.text;
document.author = this.author;
document.imageList = this.imageList;
return document;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
//....省略get、set
}
非常簡單,我們讓WordDocument實現(xiàn)了Cloneable接口并重寫了clone()方法,在clone()中進行對象的克隆操作。
那么接下來我們來實際演示下克隆效果:
//創(chuàng)建對象
WordDocument document = new WordDocument();
document.setAuthor("孟遠");
document.setText("這是一篇極好的文章。");
document.addImage("圖0");
document.addImage("圖1");
//打印創(chuàng)建的對象
tv_clone_0.append("創(chuàng)建的對象:\n" + document.toString());
//克隆對象
WordDocument cloneBean = document.clone();
//打印克隆的對象
tv_clone_0.append("克隆的對象:\n" + cloneBean.toString());
//修改克隆的對象
cloneBean.setAuthor("黑色小老虎");
cloneBean.addImage("新加圖片2");
//再次打印2個對象
tv_clone_0.append("修改克隆對象:\n" + cloneBean.toString());
tv_clone_0.append("最開始的對象:\n" + document.toString());
這段代碼我們創(chuàng)建了一篇文章,之后拷貝了這篇文章并修改了拷貝文章。
在這期間,一共進行了4次對象的toString:
- 創(chuàng)建對象完成時打印了創(chuàng)建對象;
- 拷貝完成時打印了拷貝對象;
- 修改拷貝對象完成時打印了拷貝對象;
- 最后再次打印最初創(chuàng)建的對象。
這里請注意,我們修改拷貝對象時,修改了作者姓名并且添加了一張圖片。
最后打印的結果如下:

細心的同學會發(fā)現(xiàn),我們修改了拷貝對象的作者昵稱,原對象沒有受到影響。但是我們增加一張圖片到拷貝對象的集合中時,原對象也發(fā)生了變化。
這就牽扯到了淺拷貝和深拷貝。
深拷貝
上述簡單實例,是使用了淺拷貝來實現(xiàn)的。所謂淺拷貝就是直接引用原對象中的嵌套對象,不會去進行創(chuàng)建。
大家應該都知道對象引用的問題:
Bean a = new Bean("孟遠","23","男");
Bean b = a;
此時b對象引用了a對象,也就是說其實a和b兩個對象在堆內(nèi)存中指向的是同一個地址,當修改b時,a也必定跟著發(fā)生變化。
同理我們再回頭重新看下WordDocument的clone()代碼:
@Override
protected WordDocument clone() {
WordDocument document = null;
try {
document = (WordDocument) super.clone();
document.text = this.text;
document.author = this.author;
//問題所在,直接引用當前對象的List
document.imageList = this.imageList;
return document;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
可以發(fā)現(xiàn)我們直接引用了當前對象的List,導致在內(nèi)存中拷貝對象和最初對象的List指向了同一個地址。
上面代碼就是我們口中的淺拷貝。
那么如何解決這個問題?
很簡單,使用深拷貝,即內(nèi)部對象也使用clone():
@Override
protected WordDocument clone() {
WordDocument document = null;
try {
document = (WordDocument) super.clone();
document.text = this.text;
document.author = this.author;
//關建行
document.imageList = (ArrayList<String>) imageList.clone();
return document;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
我們點進ArrayList的源碼可以發(fā)現(xiàn),ArrayList已經(jīng)實現(xiàn)了Cloneable接口,所以我們直接調(diào)用ArrayList的clone()即可。
修改完這一行代碼之后,再次執(zhí)行上面演示代碼:

完美,可以發(fā)現(xiàn)在修改完克隆對象之后,最開始的對象已經(jīng)不會受到影響。
總結
上述演示代碼已經(jīng)上傳至GitHub。
原型模式是非常簡單的一個模式,它的核心問題就是對原始對象進行拷貝,在這個模式的使用過程中需要注意一點就是:深、淺拷貝的問題。
在實際開發(fā)過程中,為了減少錯誤,建議各位讀者在使用原型模式時盡量使用深拷貝,避免操作副本時影響到原始對象。
感謝
《Android源碼設計模式解析與實戰(zhàn)》 何紅輝、關愛民 著