原型模式的介紹
原型模式是一個創(chuàng)建型的模式,原型二字表明了該模式應該有一個樣板實例箕别,用戶從這個樣板對象中復制出一個內(nèi)部屬性一直的對象,這個過程也就是我們俗稱的“克隆”。被復制的實例就是我們所稱的“原型”,這個原型是可定制的态兴。原型模式多用于創(chuàng)建復雜的或者構(gòu)造耗時的實例,因為這種情況下疟位,復制一個已經(jīng)存在的實例可使程序運行更高效瞻润。
原型模式的定義
用原型實例指定創(chuàng)建對象的種類,并通過復制這些原型創(chuàng)建新的對象甜刻。
原型模式的使用場景
- 類初始化需要消耗非常多的資源绍撞,這個資源包括數(shù)據(jù)、硬件得院、資源等傻铣,使用原型復制避免這些消耗
- 通過new 產(chǎn)生一個對象需要非常繁瑣的數(shù)據(jù)準備或訪問權(quán)限,這時可以使用原型模式
- 一個對象需要提供給其他對象訪問祥绞,而且各個調(diào)用者可能需要修改其他值非洲,可以考慮使用原型模式復制多個對象供調(diào)用者使用,即保護性拷貝蜕径。
需要注意的是两踏,通過實現(xiàn)Cloneable
接口的原型模式在調(diào)用clone
函數(shù)構(gòu)造實例時并不一定比通過new
操作快,只有當通過new
構(gòu)造對象較為耗時或者說成本較高時兜喻,通過clone
方法才能夠獲得效率上的提升梦染。因此,在使用Cloneable
是需要考慮構(gòu)建對象的成本已經(jīng)做一些效率上的測試朴皆,當然弓坞,實現(xiàn)原型模式也不一定非要實現(xiàn)Cloneable
接口隧甚,也有其他的方式。
原型模式的UML類圖
UML類圖如下:
- Client :客戶端用戶渡冻。
-
ProtoType :抽象類或接口戚扳,聲明具備
clone
能力。 - ContretePrototype :具體的原型類族吻。
原型模式的簡單實現(xiàn)
首先創(chuàng)建一個文檔對象帽借,即WordDocument
,這個文檔中含有文字和圖片超歌。用戶經(jīng)過長時間的內(nèi)容編輯后砍艾,打算對文檔做進一步的編輯,但是巍举,這個編輯后的文檔是否被采用還不確定脆荷,因此,為了安全起見懊悯,用戶需要將當前文檔拷貝一份蜓谋,然后在文檔副本上進行修改,如此炭分,這個原始文檔就是我們上述所說的樣板實例桃焕,也就是將要被”克隆“的對象,我們稱為原型捧毛。
public class WordDocument implements Cloneable {
private String mText;
private ArrayList<String> mImages = new ArrayList<>();
public WordDocument() {
System.out.println("-------構(gòu)造函數(shù)-------");
}
public void setmText(String mText) {
this.mText = mText;
}
public void addImages(String mImages) {
this.mImages.add(mImages);
}
public void show(){
System.out.println("-------------Word Content Start-------------");
System.out.println("mText" + mText);
System.out.println("mImages" );
for (String s: mImages ) {
System.out.println("ImageName:"+s);
}
System.out.println("-------------Word Content end-------------\n");
}
@Override
protected WordDocument clone() {
try {
WordDocument doc = (WordDocument) super.clone();
doc.mText = this.mText;
doc.mImages = this.mImages;
return doc;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
//客戶端的使用
@Test
public void main() {
WordDocument document = new WordDocument();
document.setmText("這是一篇文章");
document.addImages("圖片1");
document.addImages("圖片2");
document.addImages("圖片3");
document.show();
//拷貝一份副本
WordDocument document2 = document.clone();
document2.show();
document2.setmText("這是修改過的文本2");
document2.show();
document.show();
}
}
通過WordDocument
類模擬了Word
文檔中的基本元素观堂,即文字和圖片。WordDocument
在該原型模式示例中扮演的角色為ConcreteProtoType
呀忧,而Cloneable
的角色為ProtoType
师痕。WordDocument
中的clone方法用以實現(xiàn)對象克隆,注意而账,這個方法并不是Cloneable
接口中的七兜,而是Object
中的方法。Cloneable
也是一個標識接口福扬,它表明這個類的對象是可拷貝的。而沒有實現(xiàn)Cloneable
接口去調(diào)用了clone
方法就會拋出異常惜犀。在這個實例中铛碑,通過實習Cloneable
接口和重寫clone
方法實現(xiàn)原型模式。
? 輸出結(jié)果:
-------構(gòu)造函數(shù)-------
-------------Word Content Start-------------
mText這是一篇文章
mImages
ImageName:圖片1
ImageName:圖片2
ImageName:圖片3
-------------Word Content end-------------
-------------Word Content Start-------------
mText這是一篇文章
mImages
ImageName:圖片1
ImageName:圖片2
ImageName:圖片3
-------------Word Content end-------------
-------------Word Content Start-------------
mText這是修改過的文本2
mImages
ImageName:圖片1
ImageName:圖片2
ImageName:圖片3
-------------Word Content end-------------
-------------Word Content Start-------------
mText這是一篇文章
mImages
ImageName:圖片1
ImageName:圖片2
ImageName:圖片3
-------------Word Content end-------------
結(jié)合代碼已經(jīng)運行結(jié)果虽界,document2 是通過document .clone
創(chuàng)建的汽烦,并且document2
第一次輸出的時候和document
輸出的是一樣的,即document2
是document
的一份拷貝莉御,他們的內(nèi)容是一樣的撇吞,而document2
修改的文本內(nèi)容以后并不會影響document
的文本內(nèi)容俗冻,這就保證了document
的安全性。還需要注意的是牍颈,通過clone
拷貝對象時并不會執(zhí)行構(gòu)造函數(shù)迄薄!因此,如果在構(gòu)造函數(shù)中需要一些特殊的初始化操作的類型煮岁,在使用Cloneable
實現(xiàn)拷貝時讥蔽,需要注意構(gòu)造函數(shù)不會執(zhí)行的問題。
深拷貝和淺拷貝
上述原型模式的實現(xiàn)實際上只是一個淺拷貝画机,也稱為影子拷貝冶伞。這份拷貝實際上并不是將原始文檔的所有字段都重新構(gòu)造了一份,而是副本文檔的字段引用原始文檔的字段步氏,如圖:
我們知道 A 引用 B 就是說兩個對象指向同一個地址响禽,當修改 A 時 B 也會改變,B修改時A 同樣也會改變荚醒。將測試方法修改如下:
@Test
public void main() {
WordDocument document = new WordDocument();
document.setmText("這是一篇文章");
document.addImages("圖片1");
document.addImages("圖片2");
document.addImages("圖片3");
document.show();
//拷貝一份副本
WordDocument document2 = document.clone();
document2.show();
document2.setmText("這是修改過的文本2");
document2.addImages("哈哈.png");
document2.show();
document.show();
}
輸出結(jié)果
-------構(gòu)造函數(shù)-------
-------------Word Content Start-------------
mText這是一篇文章
mImages
ImageName:圖片1
ImageName:圖片2
ImageName:圖片3
-------------Word Content end-------------
-------------Word Content Start-------------
mText這是一篇文章
mImages
ImageName:圖片1
ImageName:圖片2
ImageName:圖片3
-------------Word Content end-------------
-------------Word Content Start-------------
mText這是修改過的文本2
mImages
ImageName:圖片1
ImageName:圖片2
ImageName:圖片3
ImageName:哈哈.png
-------------Word Content end-------------
-------------Word Content Start-------------
mText這是一篇文章
mImages
ImageName:圖片1
ImageName:圖片2
ImageName:圖片3
ImageName:哈哈.png
-------------Word Content end-------------
我們發(fā)現(xiàn)芋类,最后兩個文檔信息輸出是一致的,我們在document2
添加了一張名為“哈哈.png”的照片腌且,但是梗肝,同時也顯示在document
中了,這是怎么回事呢铺董?這是因為上文中只是簡單地進行了淺拷貝巫击,引用類型的新對象 document2
的mImages
只是單純地指向了this.mImages引用,并沒有重新構(gòu)造一個mImages
對象精续,然后將原始文檔中的圖片添加到新的mImages
對象中坝锰,這樣就導致document2
中的mImages
與原始文檔中的是同一個對象,因此重付,修改了其中一個文檔也會受影響顷级。document2
的mImages
添加了新的圖片,實際上也就是往document
里添加新的圖片确垫,所以弓颈,document
里面也有“哈哈.png”
圖片文件,那么如何解決呢删掀?答案就是采用深拷貝翔冀,記在拷貝對象時,對于引用類型的字段也要采用拷貝的形式披泪,而不是單純引用的形式纤子。clone
方法修改如下:
protected WordDocument clone() {
try {
WordDocument doc = (WordDocument) super.clone();
doc.mText = this.mText;
//對mImaages 對象也調(diào)用clone函數(shù)進行拷貝
doc.mImages = ((ArrayList<String>) this.mImages.clone());
return doc;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
如上述代碼所示,將doc.mImages
指向this.mImages
的一份拷貝,而不是this.mImages
本身控硼,這樣在document2
添加圖片時并不會影響ducument
泽论,運行結(jié)果如下:
-------構(gòu)造函數(shù)-------
-------------Word Content Start-------------
mText這是一篇文章
mImages
ImageName:圖片1
ImageName:圖片2
ImageName:圖片3
-------------Word Content end-------------
-------------Word Content Start-------------
mText這是一篇文章
mImages
ImageName:圖片1
ImageName:圖片2
ImageName:圖片3
-------------Word Content end-------------
-------------Word Content Start-------------
mText這是修改過的文本2
mImages
ImageName:圖片1
ImageName:圖片2
ImageName:圖片3
ImageName:哈哈.png
-------------Word Content end-------------
-------------Word Content Start-------------
mText這是一篇文章
mImages
ImageName:圖片1
ImageName:圖片2
ImageName:圖片3
-------------Word Content end-------------
原型模式是非常簡單的一個模式,它的核心問題就是對原始對象進行拷貝卡乾,在這個模式的使用過程中需要注意的一點就是:深淺拷貝的問題翼悴。在開發(fā)中為了減少錯誤,建議在使用該模式是盡量使用深拷貝说订,避免操作副本是影響原始對象的問題抄瓦。
總結(jié)
原始模式本質(zhì)就是對象拷貝,與C++
中的拷貝構(gòu)造函數(shù)有些類似陶冷,他們之間容易出現(xiàn)的問題也都是深钙姊、淺拷貝。使用原型模式可以解決構(gòu)建復雜對象的資源消耗問題埂伦,能夠在某些場景下提升創(chuàng)建對象的效率煞额。還有一個重要的用途就是保護性拷貝,也就是某個對象對外可能是只讀的沾谜。為了防止外部對這個只讀對象修改膊毁,通常可以通過返回一個對象拷貝的形式實現(xiàn)只讀的限制基跑。
優(yōu)點
原型模式是在內(nèi)存中二進制流的拷貝婚温,要比直接new
一個對象性能好很多,特別是在一個循環(huán)體內(nèi)產(chǎn)生大量的對象時媳否,原型模式可以更好地體現(xiàn)其優(yōu)點栅螟。
缺點
這既是優(yōu)點也是缺點,直接在內(nèi)存中拷貝篱竭,構(gòu)造函數(shù)是不會執(zhí)行的力图。開發(fā)中應該注意這個潛在的問題。
Demo
參考
《Android源碼設(shè)計模式》