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

1 原型模式介紹

原型模式(Prototype)是一個創(chuàng)建型的模式,原型模式是有一個共有信息的樣板實例跨释,然后拷貝這個樣板實例胸私,而復(fù)制后的實例就是所謂的“原型”,這個原型是可以修改的鳖谈。原型模式多用于創(chuàng)建復(fù)雜的或者構(gòu)造耗時的實例岁疼,因為這種情況下, 復(fù)制一個已經(jīng)存在的實例可以使程序運行更高效缆娃。

2 原型模式定義

用原型實例指定創(chuàng)建對象的種類捷绒,并通過拷貝這些原型創(chuàng)建新的對象

3 原型模式UML類圖

原型模式-UML類圖

在原型模式中有如下角色:

  • Client:客戶端角色。
  • Prototype:抽象原型角色贯要,抽象類或者接口暖侨,用來聲明clone方法。
  • ConcretePrototype:具體的原型類崇渗,是客戶端角色使用的對象字逗,即被復(fù)制的對象。

4 原型模式的使用場景

  1. 類初始化需要消耗非常多的資源宅广,這個資源包括數(shù)據(jù)葫掉、硬件資源等。通過原型拷貝避免這些消耗跟狱。
  2. 通過new產(chǎn)生一個獨享需要非常頻繁的數(shù)據(jù)準(zhǔn)備或訪問權(quán)限俭厚。
  3. 一個對象需要提供給其他對象訪問,而且各個調(diào)用者可能都需要修改其值時驶臊,可以考慮使用原型模式拷貝多個對象供調(diào)用者使用挪挤,即保護(hù)性拷貝叼丑。

5 原型模式使用示例

下面我們模擬一個發(fā)送短信的例子,來看下原型模式的簡單使用:
其中Message類扮演的是ConcretePrototype扛门,Cloneable就是Prototype
具體的原型類:

public class Message implements Cloneable{
    private String name;
    private double money;

    public Message() {
        System.out.println("執(zhí)行構(gòu)造函數(shù)Message");
    }

    public void setMessage(String name, double money) {
        this.name = name;
        this.money = money;
    }

    @NonNull
    @Override
    public Message clone() {
        Message message = null;
        try {
            message = (Message) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

        return message;
    }

    public void sendMessage(){
        System.out.println(name +"您好:您今天消費了"+money+"元");
    }
}

Message 類實現(xiàn)了Cloneable接口鸠信,它是一個標(biāo)識接口,表示這個對象是可拷貝的尖飞,只要重寫clone方法就可以實現(xiàn)拷貝症副。

這里需要注意的是clone方法并不是Cloneable接口中的店雅,而是Object中的方法政基。

下面我們看下客戶端實現(xiàn):

public class MbClient {
    public static void main(String[] args){
        Message message = new Message();
        message.setMessage("張三",100);

        Message message1 = message.clone();
        message1.setMessage("李四",200);

        Message message2 = message.clone();
        message2.setMessage("王五",300);

        message.sendMessage();
        message1.sendMessage();
        message2.sendMessage();
    }
}

我們可以看到李四和王五的消息是通過clone方法克隆的,而clone方法是不會執(zhí)行構(gòu)造函數(shù)的闹啦。輸出的結(jié)果如下:

//執(zhí)行構(gòu)造函數(shù)Message
//張三您好:您今天消費了100.0元
//李四您好:您今天消費了200.0元
//王五您好:您今天消費了300.0元

如果Message 類沒有實現(xiàn)了Cloneable接口沮明,就去直接調(diào)用clone方法,就會拋出異常窍奋。輸出結(jié)果如下:

執(zhí)行構(gòu)造函數(shù)Message
java.lang.CloneNotSupportedException: com.monkey.myapplication.mbdemo.Message
    at java.lang.Object.clone(Native Method)
    at com.monkey.myapplication.mbdemo.Message.clone(Message.java:27)
    at com.monkey.myapplication.mbdemo.MbClient.main(MbClient.java:13)
Exception in thread "main" java.lang.NullPointerException
    at com.monkey.myapplication.mbdemo.MbClient.main(MbClient.java:14)

6 淺拷貝和深拷貝

由于Object類提供的clone方法荐健,不會拷貝對象中的內(nèi)部數(shù)組和引用對象,所以就有了淺拷貝和深拷貝琳袄。

6.1 淺拷貝

我們繼續(xù)使用發(fā)送短信的例子來看下淺拷貝江场,在Message類中有一個消費明細(xì)對象。

public class Message implements Cloneable{
    private String name; //姓名
    private ExpenseDetail detail;//消費明細(xì)

    public Message() {
        System.out.println("執(zhí)行構(gòu)造函數(shù)Message");
        detail = new ExpenseDetail();
    }

    public void setMessage(String name, String type,double money) {
        this.name = name;
        this.detail.setType(type);
        this.detail.setMoney(money);
    }

    @NonNull
    @Override
    public Message clone() {
        Message message = null;
        try {
            message = (Message) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

        return message;
    }

    public void sendMessage(){
        System.out.println(name +"您好:您今天"+detail.getType()+"消費了"+detail.getMoney()+"元");
    }
}

消費明細(xì)類

public class ExpenseDetail{
    private String type;
    private double money;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }
}

客戶端類

public class MbClient {
    public static void main(String[] args){
        Message message = new Message();
        message.setMessage("張三","吃飯",10);

        Message message1 = message.clone();
        message1.setMessage("李四","看電影",50);

        Message message2 = message.clone();
        message2.setMessage("王五","買書",100);

        message.sendMessage();
        message1.sendMessage();
        message2.sendMessage();
    }
}

輸出的結(jié)果如下:

執(zhí)行構(gòu)造函數(shù)Message
張三您好:您今天買書消費了100.0元
李四您好:您今天買書消費了100.0元
王五您好:您今天買書消費了100.0元

我們可以看到所有人的消費明細(xì)居然都一樣窖逗,這是因為Object類提供的clone方法址否,不會拷貝對象中的內(nèi)部數(shù)組和引用對象,導(dǎo)致它們?nèi)耘f指向原來對象的內(nèi)部元素地址碎紊,這種拷貝叫做淺拷貝佑附。
由此而導(dǎo)致最后一次的值會覆蓋前一次的值。

6.2 深拷貝

public class Message implements Cloneable{
    ...
    
    @NonNull
    @Override
    public Message clone() {
        Message message = null;
        try {
            message = (Message) super.clone();
            message.detail = this.detail.clone();//拷貝消費明細(xì)
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

        return message;
    }
    ...

}

public class ExpenseDetail implements Cloneable{
    private String type;
    private double money;
    ...
    @NonNull
    @Override
    protected ExpenseDetail clone(){

        ExpenseDetail detail = null;
        try {
            detail = (ExpenseDetail) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

        return detail;
    }
}

使用淺拷貝的客戶端代碼再次執(zhí)行后仗考,輸出的結(jié)果如下:

執(zhí)行構(gòu)造函數(shù)Message
張三您好:您今天吃飯消費了10.0元
李四您好:您今天看電影消費了50.0元
王五您好:您今天買書消費了100.0元

拷貝Message對象的同時音同,也將它內(nèi)部的引用對象ExpenseDetail進(jìn)行拷貝,使得每個拷貝的對象之間無任何關(guān)聯(lián)秃嗜,都指向了自身對應(yīng)的ExpenseDetail权均,這種拷貝就是深拷貝。

7 總結(jié)

原型模式本質(zhì)上就是對象拷貝锅锨。使用原型模式可以解決構(gòu)建復(fù)雜對象的資源消耗問題螺句,能夠在某些場景下提升創(chuàng)建對象的效率,還有一個特點就是保護(hù)性拷貝橡类,如果我們操作時蛇尚,不會對原有的對象造成影響。

優(yōu)點:
原型模式是在內(nèi)存中二進(jìn)制流的拷貝顾画,要比new一個對象的性能要好取劫,特別是需要生產(chǎn)大量對象時匆笤。

缺點:
直接在內(nèi)存中拷貝,構(gòu)造函數(shù)是不會執(zhí)行的谱邪,這樣就減少了約束炮捧,既是優(yōu)點也是缺點,在實際開發(fā)當(dāng)中應(yīng)注意這個問題惦银。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末咆课,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子扯俱,更是在濱河造成了極大的恐慌书蚪,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件迅栅,死亡現(xiàn)場離奇詭異殊校,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)读存,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門为流,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人让簿,你說我怎么就攤上這事敬察。” “怎么了尔当?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵莲祸,是天一觀的道長。 經(jīng)常有香客問我居凶,道長虫给,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任侠碧,我火速辦了婚禮抹估,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘弄兜。我一直安慰自己药蜻,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布替饿。 她就那樣靜靜地躺著语泽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪视卢。 梳的紋絲不亂的頭發(fā)上踱卵,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼惋砂。 笑死妒挎,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的西饵。 我是一名探鬼主播酝掩,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼眷柔!你這毒婦竟也來了期虾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤驯嘱,失蹤者是張志新(化名)和其女友劉穎镶苞,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宙拉,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡宾尚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年丙笋,在試婚紗的時候發(fā)現(xiàn)自己被綠了谢澈。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡御板,死狀恐怖锥忿,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情怠肋,我是刑警寧澤敬鬓,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站笙各,受9級特大地震影響钉答,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜杈抢,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一数尿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧惶楼,春花似錦右蹦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至豹储,卻和暖如春贷盲,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背剥扣。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工巩剖, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留慨灭,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓球及,卻偏偏與公主長得像氧骤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子吃引,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容