一、介紹,定義
原型模式(Prototype Pattern)是用于創(chuàng)建重復(fù)的對象殊者,同時又能保證性能与境。
這種模式是實現(xiàn)了一個原型接口,該接口用于創(chuàng)建當(dāng)前對象的克隆猖吴。當(dāng)直接創(chuàng)建對象的代價比較大時摔刁,則采用這種模式。例如海蔽,一個對象需要在一個高代價的數(shù)據(jù)庫操作之后被創(chuàng)建共屈。我們可以緩存該對象,在下一個請求時返回它的克隆党窜,在需要的時候更新數(shù)據(jù)庫拗引,以此來減少數(shù)據(jù)庫調(diào)用。
二幌衣、使用場景
1類初始化需要消耗非常多的資源矾削,包括數(shù)據(jù)庫、硬件資源等通過原型復(fù)制避免這些消耗豁护。
2通過new產(chǎn)生一個對象需要非常繁瑣的數(shù)據(jù)準(zhǔn)備或訪問權(quán)限,這時可以使用原型模式;
3一個對象需要提供給其他對象訪問,而且各個調(diào)用者可能都需要修改其值時,可以考慮使用原型模式拷貝多個對象共調(diào)用者使用,即保護(hù)性拷貝.
通過實現(xiàn)Cloneable接口的原型模式在調(diào)用clone函數(shù)構(gòu)造實例時并不一定比new快哼凯,只有new的成本較高、耗時時楚里,通過clone方法才能獲得效率上的提升断部。
注:Android系統(tǒng)中很多地方都用到了原型模式,除此之外班缎,像比較有名的OkHttp蝴光、realm數(shù)據(jù)庫…都用到了原型模式
三、原型模式UML類圖
Client:客戶端
Prototype:抽象類或者接口达址,聲明具備clone能力
Concreteprototype:具體的原型類
四蔑祟、原型模式簡單實現(xiàn)
例子中首先創(chuàng)建了一個文檔對象,即WordDocument苏携,這個文檔中含有文字和圖片做瞪。用戶經(jīng)過了長時間的內(nèi)容編輯后,打算對該文檔做進(jìn)一步的編輯右冻,但是装蓬,這個編輯后的文檔是否會被采用還不確定,因此纱扭,為了安全起見牍帚,用戶需要將當(dāng)前文檔拷貝一份,然后再在文檔副本上進(jìn)行修改乳蛾,這與《Effective Java》一書中提到的保護(hù)性拷貝有些類似暗赶,如此鄙币,這個原始文檔就是我們上述所說的樣板實例,也就是將要被“克隆”的對象蹂随,我們稱為原型:
/**
* 文檔類型十嘿,扮演的是ConcretePrototype角色,而cloneable是代表prototype角色
*/
public class WordDocument implements Cloneable {
//文本
private String mText;
//圖片名列表
private ArrayList<String> mImages = new ArrayList<String>();
public WordDocument(){
System.out.println("-------- WordDocument構(gòu)造函數(shù) --------");
}
public String getText(){
return this.mText;
}
public void setText(String text){
this.mText = text;
}
public ArrayList<String> getImages(){
return this.mImages;
}
public void setImages(ArrayList<String> images){
this.mImages = images;
}
public void addImage(String img){
this.mImages.add(img);
}
/**
* 打印文檔
*/
public void showDocument(){
System.out.println("-------- Word Content Start --------");
System.out.println("Text : " + this.mText);
System.out.println("Images List : ");
for(String image : mImages){
System.out.println("image name : " + image);
}
System.out.println("-------- Word Content End --------");
}
@Override
protected WordDocument clone(){
try{
WordDocument doc = (WordDocument)super.clone();
doc.mText = this.mText;
doc.mImages = this.mImages;
return doc;
}catch(Exception e){}
return null;
}
}
public static void main(String[] args) throws IOException {
//1.構(gòu)建文檔對象
WordDocument originDoc = new WordDocument();
//2.編輯文檔岳锁,添加圖片等
originDoc.setText("這是一篇文檔");
originDoc.addImage("圖片一");
originDoc.addImage("圖片二");
originDoc.addImage("圖片三");
originDoc.showDocument();
//以原始文檔為原型绩衷,拷貝一份副本
WordDocument doc2 = originDoc.clone();
doc2.showDocument();
//修改文檔副本
doc2.setText("這是修改過的Doc2文本");
doc2.addImage("這是新添加的圖片");
originDoc.showDocument();
doc2.showDocument();
}
結(jié)果
-------- WordDocument構(gòu)造函數(shù) --------
//originDoc
-------- Word Content Start --------
Text : 這是一篇文檔
Images List :
image name : 圖片一
image name : 圖片二
image name : 圖片三
-------- Word Content End --------
//doc2
-------- Word Content Start --------
Text : 這是一篇文檔
Images List :
image name : 圖片一
image name : 圖片二
image name : 圖片三
-------- Word Content End --------
//副本修改后originDoc
-------- Word Content Start --------
Text : 這是一篇文檔
Images List :
image name : 圖片一
image name : 圖片二
image name : 圖片三
image name : 這是新添加的圖片
-------- Word Content End --------
//副本修改后doc2
-------- Word Content Start --------
Text : 這是修改過的Doc2文本
Images List :
image name : 圖片一
image name : 圖片二
image name : 圖片三
image name : 這是新添加的圖片
-------- Word Content End --------
這里我們發(fā)現(xiàn)通過修改doc2后,只是影響了originDoc的mImages激率,而沒有改變mText咳燕。
五、重點
clone拷貝對象并不會執(zhí)行構(gòu)造函數(shù)乒躺,當(dāng)有一些特殊初始化時招盲,需要注意;
上述原型模式的實現(xiàn)實際上只是一個淺拷貝嘉冒,也稱影子拷貝曹货,這份拷貝實際上并不是將原始的文檔的所有字段都重新構(gòu)造了一份,而是副本文檔的字段引用原始文檔的字段讳推,如下圖:
細(xì)心的讀者可能從上面的結(jié)果中發(fā)現(xiàn)控乾,最后兩個文檔信息輸出是一致的。我們在doc2添加了一張圖片娜遵,但是,同時也顯示在originDoc中壤短,這是怎么回事呢设拟?學(xué)習(xí)過C++的讀者都會有比較深刻的體會,這是因為上文中WordDocument的clone方法中只是簡單的進(jìn)行了淺拷貝久脯,引用類型的新對象doc2.mImages只是單純的指向了this.mImages引用纳胧,并沒有重新構(gòu)造一個mImages對象,然后將原始文檔中的圖片添加到新的mImages對象中帘撰,這樣就導(dǎo)致doc2.mImages與原始文檔中的是同一個對象跑慕,因此,修改了其中一個文檔中的圖片摧找,另一個文檔也會受影響核行。那么如何解決這個問題呢?答案就是采用深拷貝蹬耘,即在拷貝對象時芝雪,對于引用型的字段也要采用拷貝的形式,而不是單純引用的形式综苔。
clone方法修改如下(其他不變):
@Override
protected WordDocument clone(){
try{
WordDocument doc = (WordDocument)super.clone();
doc.mText = this.mText;
//對mImages對象也調(diào)用clone()函數(shù)惩系,進(jìn)行深拷貝
doc.mImages = (ArrayList<String>)this.mImages.clone();
return doc;
}catch(Exception e){}
return null;
}
修改后在執(zhí)行上述代碼的結(jié)果是:
-------- WordDocument構(gòu)造函數(shù) --------
//originDoc
-------- Word Content Start --------
Text : 這是一篇文檔
Images List :
image name : 圖片一
image name : 圖片二
image name : 圖片三
-------- Word Content End --------
//doc2
-------- Word Content Start --------
Text : 這是一篇文檔
Images List :
image name : 圖片一
image name : 圖片二
image name : 圖片三
-------- Word Content End --------
//副本修改后originDoc
-------- Word Content Start --------
Text : 這是一篇文檔
Images List :
image name : 圖片一
image name : 圖片二
image name : 圖片三
-------- Word Content End --------
//副本修改后doc2
-------- Word Content Start --------
Text : 這是修改過的Doc2文本
Images List :
image name : 圖片一
image name : 圖片二
image name : 圖片三
image name : 這是新添加的圖片
-------- Word Content End --------
可以看出現(xiàn)在互不影響位岔,這個叫做深拷貝。
接著上面的疑問堡牡,其實String類型在淺拷貝時和引用類型一樣抒抬,沒有單獨復(fù)制,而是引用同一地址晤柄,因為String沒有實現(xiàn)cloneable接口擦剑,也就是說只能復(fù)制引用。(這里我們可以查看源碼可以看到可免,而ArrayList實現(xiàn)了cloneable接口)但是當(dāng)修改其中的一個值的時候抓于,會新分配一塊內(nèi)存用來保存新的值,這個引用指向新的內(nèi)存空間浇借,原來的String因為還存在指向他的引用捉撮,所以不會被回收,這樣妇垢,雖然是復(fù)制的引用巾遭,但是修改值的時候,并沒有改變被復(fù)制對象的值闯估。
所以在很多情況下灼舍,我們可以把String在clone的時候和基本類型做相同的處理,只是在equals時注意一些就行了涨薪。
原型模式是非常簡單的一個模式骑素,它的核心問題就是對原始對象進(jìn)行拷貝,在這個模式的使用過程中需要注意的一點就是:深刚夺、淺拷貝的問題献丑。在開發(fā)過程中,為了減少錯誤侠姑,作者建議使用該模式時盡量使用深拷貝创橄,避免操作副本時影響原始對象的問題。
六莽红、總結(jié)
原型模式本質(zhì)上就是對象的拷貝妥畏,與C++中的拷貝構(gòu)造函數(shù)有些類似,它們之間容易出現(xiàn)的問題也都是深拷貝安吁、淺拷貝醉蚁。使用原型模式可以解決構(gòu)建復(fù)雜對象的資源消耗問題,能夠在某些場景下提升創(chuàng)建對象的效率柳畔。
優(yōu)點:
(1)原型模式是在內(nèi)存中二進(jìn)制流的拷貝馍管,要比直接new一個對象性能好很多,特別是要在一個循環(huán)體內(nèi)產(chǎn)生大量對象時薪韩,原型模式可能更好的體現(xiàn)其優(yōu)點确沸。
(2)還有一個重要的用途就是保護(hù)性拷貝捌锭,也就是對某個對象對外可能是只讀的,為了防止外部對這個只讀對象的修改罗捎,通彻矍可以通過返回一個對象拷貝的形式實現(xiàn)只讀的限制。
缺點:
(1)這既是它的優(yōu)點也是缺點桨菜,直接在內(nèi)存中拷貝豁状,構(gòu)造函數(shù)是不會執(zhí)行的,在實際開發(fā)中應(yīng)該注意這個潛在問題倒得。優(yōu)點是減少了約束泻红,缺點也是減少了約束,需要大家在實際應(yīng)用時考慮霞掺。
(2)通過實現(xiàn)Cloneable接口的原型模式在調(diào)用clone函數(shù)構(gòu)造實例時并不一定比通過new操作速度快谊路,只有當(dāng)通過new構(gòu)造對象較為耗時或者說成本較高時,通過clone方法才能夠獲得效率上的提升菩彬。