Android編程設(shè)計(jì)模式之原型模式實(shí)例詳解
本文實(shí)例講述了Android編程設(shè)計(jì)模式之原型模式。分享給大家供大家參考,具體如下:
一、介紹
原型模式是一個(gè)創(chuàng)建型的模式。原型二字表明了該模型應(yīng)該有一個(gè)樣板實(shí)例,用戶從這個(gè)樣板對(duì)象中復(fù)制出一個(gè)內(nèi)部屬性一致的對(duì)象,這個(gè)過程也就是我們俗稱的“克隆”。被復(fù)制的實(shí)例就是我們所稱的“原型”,這個(gè)原型也是可定制的。原型模型多用于創(chuàng)建復(fù)雜的或者構(gòu)造耗時(shí)的實(shí)例,因?yàn)檫@種情況下,復(fù)制一個(gè)已經(jīng)存在的實(shí)例可使程序運(yùn)行更高效。
二、定義
用原型實(shí)例指定創(chuàng)建對(duì)象的種類,并通過拷貝這些原型創(chuàng)建新的對(duì)象。
三、使用場(chǎng)景
(1)類初始化需要消耗非常多的資源,這個(gè)資源包括數(shù)據(jù)、硬件資源等,通過原型拷貝避免這些消耗。
(2)通過new產(chǎn)生一個(gè)對(duì)象需要非常繁瑣的數(shù)據(jù)準(zhǔn)備或訪問權(quán)限,這時(shí)可以使用原型模式。
(3)一個(gè)對(duì)象需要提供給其他對(duì)象訪問,而且各個(gè)調(diào)用者可能都需要修改其值時(shí),可以考慮使用原型模式拷貝多個(gè)對(duì)象供調(diào)用者使用,即保護(hù)性拷貝。
需要注意的是,通過實(shí)行Cloneable接口的原型模式在調(diào)用clone函數(shù)構(gòu)造實(shí)例時(shí)并不一定比通過new操作速度快,只有當(dāng)通過new構(gòu)造對(duì)象較為耗時(shí)或者說成本較高時(shí),通過clone方法才能夠獲得效率上的提升。因此,在使用Cloneable時(shí)需要考慮構(gòu)建對(duì)象的成本以及做一些效率上的測(cè)試。當(dāng)然,實(shí)現(xiàn)原型模式也不一定非要實(shí)現(xiàn)Cloneable接口,也有其他的實(shí)現(xiàn)方式,這里將會(huì)對(duì)這些一一說明。
四、原型模型的UML類圖
圖中角色介紹:
Client:客戶端用戶。
Prototype:抽象類或者接口,聲明具備clone能力。
ConcretePrototype:具體的原型類。
五、原型模式的簡(jiǎn)單實(shí)現(xiàn)
下面以簡(jiǎn)單的文檔拷貝為例來演示一下簡(jiǎn)單的原型模式,我們?cè)谶@個(gè)例子中首先創(chuàng)建了一個(gè)文檔對(duì)象,即WordDocument,這個(gè)文檔中含有文字和圖片。用戶經(jīng)過了長(zhǎng)時(shí)間的內(nèi)容編輯后,打算對(duì)該文檔做進(jìn)一步的編輯,但是,這個(gè)編輯后的文檔是否會(huì)被采用還不確定,因此,為了安全起見,用戶需要將當(dāng)前文檔拷貝一份,然后再在文檔副本上進(jìn)行修改,這與《Effective Java》一書中提到的保護(hù)性拷貝有些類似,如此,這個(gè)原始文檔就是我們上述所說的樣板實(shí)例,也就是將要被“克隆”的對(duì)象,我們成為原型:
示例代碼:
/** * 文檔類型,扮演的是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; } }
執(zhí)行方法:
public static void main(String[] args) throws IOException { //1.構(gòu)建文檔對(duì)象 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(); }
執(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 : 圖片三 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。
六、淺拷貝和深拷貝
上述原型模式的實(shí)現(xiàn)實(shí)際上只是一個(gè)淺拷貝,也稱影子拷貝,這份拷貝實(shí)際上并不是將原始的文檔的所有字段都重新構(gòu)造了一份,而是副本文檔的字段引用原始文檔的字段,如下圖:
細(xì)心的讀者可能從上面的結(jié)果中發(fā)現(xiàn),最后兩個(gè)文檔信息輸出是一致的。我們?cè)赿oc2添加了一張圖片,但是,同時(shí)也顯示在originDoc中,這是怎么回事呢?學(xué)習(xí)過C++的讀者都會(huì)有比較深刻的體會(huì),這是因?yàn)樯衔闹蠾ordDocument的clone方法中只是簡(jiǎn)單的進(jìn)行了淺拷貝,引用類型的新對(duì)象doc2.mImages只是單純的指向了this.mImages引用,并沒有重新構(gòu)造一個(gè)mImages對(duì)象,然后將原始文檔中的圖片添加到新的mImages對(duì)象中,這樣就導(dǎo)致doc2.mImages與原始文檔中的是同一個(gè)對(duì)象,因此,修改了其中一個(gè)文檔中的圖片,另一個(gè)文檔也會(huì)受影響。那么如何解決這個(gè)問題呢?答案就是采用深拷貝,即在拷貝對(duì)象時(shí),對(duì)于引用型的字段也要采用拷貝的形式,而不是單純引用的形式。
clone方法修改如下(其他不變):
@Override protected WordDocument clone(){ try{ WordDocument doc = (WordDocument)super.clone(); doc.mText = this.mText; //對(duì)mImages對(duì)象也調(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)在互不影響,這個(gè)叫做深拷貝。
接著上面的疑問,其實(shí)String類型在淺拷貝時(shí)和引用類型一樣,沒有單獨(dú)復(fù)制,而是引用同一地址,因?yàn)镾tring沒有實(shí)現(xiàn)cloneable接口,也就是說只能復(fù)制引用。(這里我們可以查看源碼可以看到,而ArrayList實(shí)現(xiàn)了cloneable接口)但是當(dāng)修改其中的一個(gè)值的時(shí)候,會(huì)新分配一塊內(nèi)存用來保存新的值,這個(gè)引用指向新的內(nèi)存空間,原來的String因?yàn)檫€存在指向他的引用,所以不會(huì)被回收,這樣,雖然是復(fù)制的引用,但是修改值的時(shí)候,并沒有改變被復(fù)制對(duì)象的值。
所以在很多情況下,我們可以把String在clone的時(shí)候和基本類型做相同的處理,只是在equals時(shí)注意一些就行了。
原型模式是非常簡(jiǎn)單的一個(gè)模式,它的核心問題就是對(duì)原始對(duì)象進(jìn)行拷貝,在這個(gè)模式的使用過程中需要注意的一點(diǎn)就是:深、淺拷貝的問題。在開發(fā)過程中,為了減少錯(cuò)誤,作者建議使用該模式時(shí)盡量使用深拷貝,避免操作副本時(shí)影響原始對(duì)象的問題。
七、Android源碼中的原型模式
示例代碼:
Uri uri = Uri.parse("smsto:110"); Intent intent = new Intent(Intent.ACTION_SEND,uri); intent.putExtra("sms_body", "The SMS text"); //克隆 Intent intent2 = (Intent)intent.clone(); startActivity(intent2);
八、總結(jié)
原型模式本質(zhì)上就是對(duì)象的拷貝,與C++中的拷貝構(gòu)造函數(shù)有些類似,它們之間容易出現(xiàn)的問題也都是深拷貝、淺拷貝。使用原型模式可以解決構(gòu)建復(fù)雜對(duì)象的資源消耗問題,能夠在某些場(chǎng)景下提升創(chuàng)建對(duì)象的效率。
優(yōu)點(diǎn):
(1)原型模式是在內(nèi)存中二進(jìn)制流的拷貝,要比直接new一個(gè)對(duì)象性能好很多,特別是要在一個(gè)循環(huán)體內(nèi)產(chǎn)生大量對(duì)象時(shí),原型模式可能更好的體現(xiàn)其優(yōu)點(diǎn)。
(2)還有一個(gè)重要的用途就是保護(hù)性拷貝,也就是對(duì)某個(gè)對(duì)象對(duì)外可能是只讀的,為了防止外部對(duì)這個(gè)只讀對(duì)象的修改,通常可以通過返回一個(gè)對(duì)象拷貝的形式實(shí)現(xiàn)只讀的限制。
缺點(diǎn):
(1)這既是它的優(yōu)點(diǎn)也是缺點(diǎn),直接在內(nèi)存中拷貝,構(gòu)造函數(shù)是不會(huì)執(zhí)行的,在實(shí)際開發(fā)中應(yīng)該注意這個(gè)潛在問題。優(yōu)點(diǎn)是減少了約束,缺點(diǎn)也是減少了約束,需要大家在實(shí)際應(yīng)用時(shí)考慮。
(2)通過實(shí)現(xiàn)Cloneable接口的原型模式在調(diào)用clone函數(shù)構(gòu)造實(shí)例時(shí)并不一定比通過new操作速度快,只有當(dāng)通過new構(gòu)造對(duì)象較為耗時(shí)或者說成本較高時(shí),通過clone方法才能夠獲得效率上的提升。
更多關(guān)于Android相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《Android開發(fā)入門與進(jìn)階教程》、《Android調(diào)試技巧與常見問題解決方法匯總》、《Android基本組件用法總結(jié)》、《Android視圖View技巧總結(jié)》、《Android布局layout技巧總結(jié)》及《Android控件用法總結(jié)》
希望本文所述對(duì)大家Android程序設(shè)計(jì)有所幫助。
相關(guān)文章
Android中應(yīng)用界面主題Theme使用方法和頁(yè)面定時(shí)跳轉(zhuǎn)應(yīng)用
在Android SDK中內(nèi)置了下面的Theme,可以按標(biāo)題欄Title Bar和狀態(tài)欄Status Bar是否可見來分類,感興趣的朋友可以了解下哈2013-06-06Android使用VideoView播放本地視頻和網(wǎng)絡(luò)視頻的方法
本文將講解如何使用Android視頻播放器VideoView來播放本地視頻和網(wǎng)絡(luò)視頻,實(shí)現(xiàn)起來還是比較簡(jiǎn)單的,有需要的可以參考借鑒。2016-08-08Android RecyclerView實(shí)現(xiàn)拼團(tuán)倒計(jì)時(shí)列表實(shí)例代碼
這篇文章主要給大家介紹了關(guān)于Android RecyclerView實(shí)現(xiàn)拼團(tuán)倒計(jì)時(shí)列表的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)各位Android開發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09Android 中啟動(dòng)自己另一個(gè)程序的activity如何實(shí)現(xiàn)
這篇文章主要介紹了Android 中啟動(dòng)自己另一個(gè)程序的activity如何實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2017-04-04Android實(shí)現(xiàn)閃屏及注冊(cè)和登錄界面之間的切換效果
這篇文章主要介紹了Android實(shí)現(xiàn)閃屏及注冊(cè)和登錄界面之間的切換效果,實(shí)現(xiàn)思路是先分別實(shí)現(xiàn)閃屏、注冊(cè)界面、登錄界面的活動(dòng),再用Intent將相關(guān)的活動(dòng)連接起來,實(shí)現(xiàn)不同活動(dòng)之間的跳轉(zhuǎn),對(duì)android 實(shí)現(xiàn)閃屏和界面切換感興趣的朋友一起看看吧2016-11-11Android 創(chuàng)建與解析XML(五)——詳解Dom4j方式
本篇文章主要介紹了Android創(chuàng)建與解析XML(二)——詳解Dom4j方式,這里整理了詳細(xì)的代碼,有需要的小伙伴可以參考下。2016-11-11Android開發(fā)之關(guān)于項(xiàng)目
本文是此系列文章的第二篇,給大家介紹的是項(xiàng)目相關(guān)的內(nèi)容,非常的細(xì)致全面,有需要的小伙伴可以參考下2016-02-02Flutter開發(fā)setState能否在build中直接調(diào)用詳解
這篇文章主要為大家介紹了Flutter開發(fā)setState能否在build中直接調(diào)用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10Android 基于Socket的聊天應(yīng)用實(shí)例(二)
本篇文章主要介紹了Android 基于Socket的聊天應(yīng)用實(shí)例,具有一定的參考價(jià)值,有需要的可以了解一下。2016-12-12