設(shè)計模式之原型模式

原型模式的介紹

原型模式是一個創(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類圖如下:

原型模式1.png

  • 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 輸出的是一樣的,即document2document 的一份拷貝莉御,他們的內(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)造了一份,而是副本文檔的字段引用原始文檔的字段步氏,如圖:

淺拷貝.png

我們知道 A 引用 B 就是說兩個對象指向同一個地址响禽,當修改 AB 也會改變,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中了,這是怎么回事呢铺董?這是因為上文中只是簡單地進行了淺拷貝巫击,引用類型的新對象 document2mImages 只是單純地指向了this.mImages引用,并沒有重新構(gòu)造一個mImages對象精续,然后將原始文檔中的圖片添加到新的mImages 對象中坝锰,這樣就導致document2 中的mImages 與原始文檔中的是同一個對象,因此重付,修改了其中一個文檔也會受影響顷级。document2mImages 添加了新的圖片,實際上也就是往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

設(shè)計模式Demo

參考

《Android源碼設(shè)計模式》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末掺逼,一起剝皮案震驚了整個濱河市吃媒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌吕喘,老刑警劉巖赘那,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異氯质,居然都是意外死亡募舟,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門病梢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事蜓陌∶僬茫” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵钮热,是天一觀的道長填抬。 經(jīng)常有香客問我,道長隧期,這世上最難降的妖魔是什么飒责? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮仆潮,結(jié)果婚禮上宏蛉,老公的妹妹穿的比我還像新娘。我一直安慰自己性置,他們只是感情好拾并,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鹏浅,像睡著了一般嗅义。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上隐砸,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天之碗,我揣著相機與錄音,去河邊找鬼季希。 笑死褪那,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的胖眷。 我是一名探鬼主播武通,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼珊搀!你這毒婦竟也來了冶忱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤境析,失蹤者是張志新(化名)和其女友劉穎囚枪,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體劳淆,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡链沼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了沛鸵。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片括勺。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡缆八,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出疾捍,到底是詐尸還是另有隱情奈辰,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布乱豆,位于F島的核電站奖恰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏宛裕。R本人自食惡果不足惜瑟啃,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望揩尸。 院中可真熱鬧蛹屿,春花似錦、人聲如沸疲酌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽朗恳。三九已至湿颅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間粥诫,已是汗流浹背油航。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留怀浆,地道東北人谊囚。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像执赡,于是被迫代替她去往敵國和親镰踏。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355