Java設(shè)計(jì)模式-創(chuàng)建型模式-原型模式

此系列文章為清華大學(xué)出版社出版劉偉編著《Java設(shè)計(jì)模式》的學(xué)習(xí)筆記卢鹦。

>>全部23種設(shè)計(jì)模式<<

1 概述

原型模式是一種對(duì)象創(chuàng)建型模式,它的工作原理很簡(jiǎn)單:將一個(gè)原型對(duì)象傳給要發(fā)動(dòng)創(chuàng)建的對(duì)象(即客戶(hù)端對(duì)象)捐寥,這個(gè)發(fā)動(dòng)創(chuàng)建的對(duì)象通過(guò)請(qǐng)求原型對(duì)象復(fù)制自己來(lái)實(shí)現(xiàn)創(chuàng)建過(guò)程。

2 結(jié)構(gòu)與實(shí)現(xiàn)

2.1 原型模式結(jié)構(gòu)

原型模式包含 3 個(gè)角色

  1. AbstractPrototype (抽象原型類(lèi)):它是聲明克隆方法的接口,是所有具體原型類(lèi)的公共父類(lèi),它可以是抽象類(lèi)也可以是接口咧虎,甚至還可以是具體實(shí)現(xiàn)類(lèi)。
  2. ConcretePrototype (具體圓形類(lèi)):它實(shí)現(xiàn)在抽象原型類(lèi)中聲明的克隆方法计呈,在克隆方法中返回一個(gè)自己的克隆對(duì)象砰诵。
  3. Client (客戶(hù)類(lèi)):在客戶(hù)類(lèi)中,讓一個(gè)原型對(duì)象克隆自身從而創(chuàng)建一個(gè)新的對(duì)象捌显,只需要直接實(shí)例化或通過(guò)工廠(chǎng)方法等方式創(chuàng)建一個(gè)原型對(duì)象茁彭,再通過(guò)調(diào)用該對(duì)象的克隆方法即可得到多個(gè)相同的對(duì)象。由于客戶(hù)類(lèi)針對(duì)抽象原型類(lèi)編程扶歪,因此用戶(hù)可以根據(jù)需要選擇具體的原型類(lèi)理肺,系統(tǒng)具有較好的可擴(kuò)展性,增加或更換具體的原型類(lèi)都很方便善镰。

2.2 淺克隆與深克隆

根據(jù)在復(fù)制原型對(duì)象的同時(shí)妹萨,是否復(fù)制包含在原型對(duì)象中的引用對(duì)象成員,原型模式的克隆機(jī)制分為淺克隆深克隆炫欺。

2.2.1 淺克隆

淺克隆與深克隆的討論針對(duì)的是原型類(lèi)中的引用對(duì)象成員乎完,而淺克隆的特點(diǎn)是不會(huì)在內(nèi)存中新建一個(gè)引用對(duì)象成員,與原型類(lèi)共用該引用對(duì)象成員品洛,在原型類(lèi)執(zhí)行淺克隆時(shí)囱怕,向克隆產(chǎn)生的新對(duì)象傳遞的是原型對(duì)象的引用對(duì)象的地址,造成兩個(gè)類(lèi)共享該引用成員變量毫别。

2.2.2 深克隆

與淺克隆相反娃弓,深克隆在內(nèi)存中重新申請(qǐng)空間,克隆一個(gè)不和原型類(lèi)共享的引用成員變量岛宦。

2.2.3 Java 中的 clone() 方法和 Cloneable 接口

Java 中台丛,java.lang.Object 類(lèi)提供了一個(gè) clone() 方法,可以將一個(gè)Java對(duì)象復(fù)制一份砾肺。因此在 Java 中可以直接使用該 clone() 方法來(lái)實(shí)現(xiàn)對(duì)象的淺克隆挽霉。

需要注意的是,能夠?qū)崿F(xiàn)克隆的Java類(lèi)必須實(shí)現(xiàn)一個(gè)標(biāo)識(shí)接口 Cloneable变汪,表示這個(gè) Java 類(lèi)支持被復(fù)制侠坎。如果一個(gè)類(lèi)沒(méi)有實(shí)現(xiàn)這個(gè)接口但是調(diào)用了 clone() 方法,Java 編譯器將會(huì)拋出一個(gè) CloneNotSupportedException 異常裙盾。

Java 中的 clone() 方法滿(mǎn)足以下幾點(diǎn):

  1. 對(duì)任何對(duì)象 x实胸,都有 x.clone() != x 他嫡,即克隆對(duì)象與原型對(duì)象不是同一個(gè)對(duì)象。
  2. 對(duì)任何對(duì)象 x庐完,都有 x.clone().getClass() == x.getClass() 钢属,即克隆對(duì)象與原型對(duì)象的類(lèi)型一樣。
  3. 如果對(duì)象 x 的 equals() 方法定義恰當(dāng)门躯,那么 x.clone().equals(x) 應(yīng)該成立淆党。

利用 Object 類(lèi)的 clone() 方法,需要以下幾個(gè)步驟:

  1. 在子類(lèi)中覆蓋父類(lèi)的 clone() 方法讶凉,并聲明為 public染乌。
  2. 在子類(lèi)的 clone() 方法中調(diào)用 super.clone()。
  3. 子類(lèi)需要實(shí)現(xiàn) Cloneable 接口懂讯。

此時(shí)荷憋,Object 類(lèi)相當(dāng)于抽象原型類(lèi),所有實(shí)現(xiàn)了 Cloneable 接口的類(lèi)相當(dāng)于原型類(lèi)域醇。

2.2.4 Java 中的 Serializable 接口

實(shí)現(xiàn)深克隆的方式有很多,當(dāng)我們利用字節(jié)流來(lái)實(shí)現(xiàn)深克隆時(shí)蓉媳,原型類(lèi)需要實(shí)現(xiàn) Serializable 接口譬挚。

2.3 原型模式舉例

一、背景介紹

在使用某 OA 系統(tǒng)時(shí)酪呻,有些崗位的員工發(fā)現(xiàn)他們每周的工作都大同小異减宣,因此在填寫(xiě)工作周報(bào)時(shí)很多內(nèi)容都是重復(fù)的,為了提高工作周報(bào)的創(chuàng)建效率玩荠,大家迫切希望有一種機(jī)制能夠快速創(chuàng)建相同或者相似的周報(bào)漆腌,包括創(chuàng)建周報(bào)的附件。請(qǐng)使用原型模式實(shí)現(xiàn)阶冈。

二闷尿、項(xiàng)目結(jié)構(gòu)

項(xiàng)目結(jié)構(gòu).png

三、抽象原型類(lèi)

package CreationalPattern.PrototypePattern.AbstractPrototype;

import CreationalPattern.PrototypePattern.Attachment;
import java.io.IOException;
import java.io.Serializable;

public abstract class WeeklyLog implements Cloneable, Serializable {
    protected Attachment attachment;
    protected String name;
    protected String date;
    protected String content;

    public abstract Attachment getAttachment();

    public abstract void setAttachment(Attachment attachment);

    public abstract String getName();

    public abstract void setName(String name);

    public abstract String getDate();

    public abstract void setDate(String date);

    public abstract String getContent();

    public abstract void setContent(String content);

    //todo:shadowClone()方法實(shí)現(xiàn)淺拷貝
    public abstract WeeklyLog shadowClone();

    //todo:deepClone()方法實(shí)現(xiàn)深拷貝
    public abstract WeeklyLog deepClone() throws IOException, ClassNotFoundException;
}

四女坑、具體原型類(lèi)

package CreationalPattern.PrototypePattern.ConcretePrototype;

import CreationalPattern.PrototypePattern.AbstractPrototype.WeeklyLog;
import CreationalPattern.PrototypePattern.Attachment;
import java.io.*;

public class MyWeeklyLog extends WeeklyLog {
    protected Attachment attachment;
    protected String name;
    protected String date;
    protected String content;

    public Attachment getAttachment() {
        return attachment;
    }

    public void setAttachment(Attachment attachment) {
        this.attachment = attachment;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    //todo:shadowClone()方法實(shí)現(xiàn)淺拷貝
    public CreationalPattern.PrototypePattern.AbstractPrototype.WeeklyLog shadowClone() {
        Object obj = null;
        try{
            obj = super.clone();
            return (CreationalPattern.PrototypePattern.AbstractPrototype.WeeklyLog)obj;
        } catch (CloneNotSupportedException e) {
            System.out.println("不支持復(fù)制");
            return null;
        }
    }

    //todo:deepClone()方法實(shí)現(xiàn)深拷貝
    public WeeklyLog deepClone() throws IOException, ClassNotFoundException {
        //todo:將對(duì)象寫(xiě)入流中
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(this);
        //todo:將對(duì)象從流中讀出
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        return (WeeklyLog)ois.readObject();
    }
}

五填具、附件類(lèi)

package CreationalPattern.PrototypePattern;

import java.io.Serializable;

public class Attachment implements Serializable {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void download(){
        System.out.println("下載附件 [ "+name+" ] ");
    }
}

六、客戶(hù)類(lèi)

package CreationalPattern.PrototypePattern;

import CreationalPattern.PrototypePattern.AbstractPrototype.WeeklyLog;
import CreationalPattern.PrototypePattern.ConcretePrototype.MyWeeklyLog;
import java.io.IOException;

public class Client {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //todo:淺拷貝
        WeeklyLog preLog1,newLog1;
        preLog1 = new MyWeeklyLog();
        Attachment attachment1 = new Attachment();
        preLog1.setAttachment(attachment1);
        newLog1 = preLog1.shadowClone();
        System.out.println("WeeklyLog是否相同匆骗?"+(preLog1 == newLog1));//false
        System.out.println("Attachment是否相同劳景?"+(preLog1.getAttachment() == newLog1.getAttachment()));//true
        //todo:深拷貝
        WeeklyLog preLog2,newLog2;
        preLog2 = new MyWeeklyLog();
        Attachment attachment2 = new Attachment();
        preLog2.setAttachment(attachment2);
        newLog2 = preLog2.deepClone();
        System.out.println("WeeklyLog是否相同?"+(preLog2 == newLog2));//false
        System.out.println("Attachment是否相同碉就?"+(preLog2.getAttachment()==newLog2.getAttachment()));//false
    }
}

七盟广、測(cè)試結(jié)果

測(cè)試結(jié)果.png

3 總結(jié)

3.1 原型模式優(yōu)點(diǎn)

  1. 當(dāng)創(chuàng)建新的對(duì)象實(shí)例較為復(fù)雜時(shí),使用原型模式可以簡(jiǎn)化對(duì)象的創(chuàng)建過(guò)程瓮钥,通過(guò)復(fù)制一個(gè)已有實(shí)例可以提高新勢(shì)力的創(chuàng)建效率筋量。
  2. 擴(kuò)展性較好烹吵,由于在原型模式中提高了創(chuàng)建原型類(lèi),在客戶(hù)端可以針對(duì)抽象原型類(lèi)進(jìn)行編程毛甲,而將具體原型類(lèi)寫(xiě)在配置文件中年叮,增加或減少產(chǎn)品類(lèi)對(duì)原有系統(tǒng)沒(méi)有任何影響。
  3. 原型模式提供了簡(jiǎn)化的創(chuàng)建結(jié)構(gòu)玻募,工廠(chǎng)方法模式常常需要有一個(gè)與產(chǎn)品類(lèi)等級(jí)結(jié)構(gòu)相同的工廠(chǎng)等級(jí)結(jié)構(gòu)只损,而原型模式不需要,原型模式中產(chǎn)品的復(fù)制是通過(guò)封裝在原型內(nèi)部的克隆方法實(shí)現(xiàn)的七咧,無(wú)需專(zhuān)門(mén)的工廠(chǎng)類(lèi)跃惫。
  4. 可以使用深克隆的方式保存對(duì)象的狀態(tài),使用原型模式將對(duì)象復(fù)制一份并將其狀態(tài)保存起來(lái)艾栋,以便在需要的時(shí)候使用(例如恢復(fù)到某一歷史狀態(tài))爆存,可輔助實(shí)現(xiàn)撤銷(xiāo)操作。

3.2 原型模式缺點(diǎn)

  1. 需要為每一個(gè)類(lèi)配備一個(gè)克隆方法蝗砾,而且該克隆方法位于一個(gè)類(lèi)的內(nèi)部先较,當(dāng)對(duì)已有的類(lèi)進(jìn)行改造時(shí)需要修改源代碼,違背了開(kāi)閉原則悼粮。
  2. 在實(shí)現(xiàn)深克隆時(shí)闲勺,需要編寫(xiě)較為復(fù)雜的代碼,而且當(dāng)對(duì)象之間存在多重的嵌套引用時(shí)扣猫,為了實(shí)現(xiàn)深克隆菜循,每一次對(duì)象對(duì)應(yīng)的類(lèi)都必須支持深克隆,實(shí)現(xiàn)起來(lái)可能會(huì)比較麻煩申尤。

3.3 原型模式適用環(huán)境

  1. 創(chuàng)建對(duì)象成本較大(例如初始化需要占用較長(zhǎng)的時(shí)間癌幕、占用太多的CPU資源或者網(wǎng)絡(luò)資源),新對(duì)象可以通過(guò)復(fù)制已有對(duì)象來(lái)獲得昧穿,如果是相似對(duì)象勺远,則可以對(duì)其成員變量稍作修改。
  2. 系統(tǒng)要保存對(duì)象的狀態(tài)时鸵,而對(duì)象的狀態(tài)變化很小谚中。
  3. 需要避免使用分層次的工廠(chǎng)類(lèi)來(lái)創(chuàng)建分層次的對(duì)象,并且類(lèi)的實(shí)例對(duì)象只有一個(gè)或很少幾個(gè)組合狀態(tài)寥枝,通過(guò)復(fù)制原型對(duì)象得到新實(shí)例可能比使用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例更加方便宪塔。

>>全部23種設(shè)計(jì)模式<<

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市囊拜,隨后出現(xiàn)的幾起案子某筐,更是在濱河造成了極大的恐慌,老刑警劉巖冠跷,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件南誊,死亡現(xiàn)場(chǎng)離奇詭異身诺,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)抄囚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)霉赡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人幔托,你說(shuō)我怎么就攤上這事穴亏。” “怎么了重挑?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵嗓化,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我谬哀,道長(zhǎng)刺覆,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任史煎,我火速辦了婚禮谦屑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘篇梭。我一直安慰自己氢橙,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布很洋。 她就那樣靜靜地躺著充蓝,像睡著了一般隧枫。 火紅的嫁衣襯著肌膚如雪喉磁。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,985評(píng)論 1 291
  • 那天官脓,我揣著相機(jī)與錄音协怒,去河邊找鬼。 笑死卑笨,一個(gè)胖子當(dāng)著我的面吹牛孕暇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播赤兴,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼妖滔,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了桶良?” 一聲冷哼從身側(cè)響起座舍,我...
    開(kāi)封第一講書(shū)人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎陨帆,沒(méi)想到半個(gè)月后曲秉,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體采蚀,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年承二,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了榆鼠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡亥鸠,死狀恐怖妆够,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情读虏,我是刑警寧澤责静,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站盖桥,受9級(jí)特大地震影響灾螃,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜揩徊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一腰鬼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧塑荒,春花似錦熄赡、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至凌箕,卻和暖如春拧篮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背牵舱。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工串绩, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芜壁。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓礁凡,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親慧妄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子顷牌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

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