Java 設(shè)計模式-原型模式(克隆)

一窃判、什么是原型模式

原型模式(Prototype)燥爷,用原型實例指定創(chuàng)建對象的種類,并且通過拷貝這些原型創(chuàng)建新的對象谅辣。簡單說修赞,就是從一個對象再創(chuàng)建另外一個可定制的對象,而且不需要知道任何創(chuàng)建細(xì)節(jié)桑阶。

二柏副、原型的引入克隆

現(xiàn)在我們有一個需求,要求有一個簡歷類蚣录,必須要有姓名割择,可以設(shè)置性別和年齡,可以設(shè)置工作經(jīng)歷萎河。最終我們需要寫三份簡歷荔泳。

通常,在不用原型模式的情況下虐杯,是這樣實現(xiàn)的玛歌。方式一:
簡歷類

class Resume
{
   private String name;
   private String gender;
   private String age;
   private String timeArea;
   private String company;
   
  public Resume(String name){
      this.name = name;
  }

  public void setPersonInfo(String gender,String age){
     this.gender = gender;
     this.age = age;
  } 

  public void setWorkExperience(String timeArea, String company){
     this.timeArea = timeArea;
     this.company = company;
  }

  public void display(){
     System.out.print(name +" "+ gender +" "+age);
     System.out.print("工作經(jīng)歷 "+ timeArea +" "+company);
  }

}

客戶端調(diào)用代碼

static void main(String[] args){//此處也可為for循環(huán)創(chuàng)建
    Resume a = new Resume("張三");
    a.setPersonInfo("男" , "30");
    a.setWorkExperience("2010-2018" , "xx公司");

    Resume b = new Resume("張三");
    b.setPersonInfo("男" , "30");
    b.setWorkExperience("2010-2018" , "xx公司");

    Resume c = new Resume("張三");
    c.setPersonInfo("男" , "30");
    c.setWorkExperience("2010-2018" , "xx公司");

    a.display();
    b.display();
    c.display();

}

結(jié)果顯示

張三 男 30
工作經(jīng)歷 2010-2018 xx公司
張三 男 30
工作經(jīng)歷 2010-2018 xx公司
張三 男 30
工作經(jīng)歷 2010-2018 xx公司

這里我們看到3份簡歷需要三次實例化,這樣是不是有點(diǎn)麻煩擎椰,如果需要100份沾鳄,那我們就要實例化100次。一旦寫錯了一個字确憨,都要改100次译荞。
當(dāng)然我們想偷懶一下,可以這樣寫休弃。

    Resume a = new Resume("張三");
    a.setPersonInfo("男" , "30");
    a.setWorkExperience("2010-2018" , "xx公司");
    
    Resume b = a;//傳引用而不傳值
    Resume c = a;
    
    a.display();
    b.display();
    c.display();
    

以上創(chuàng)建對象是通過new方式創(chuàng)建的吞歼,但是在我們熟知的概念中,還有一種創(chuàng)建對象的方式是克隆方法塔猾。顧名思義篙骡,是通過已有的對象復(fù)制一個新的對象一模一樣的。即我們上面原型模式的定義,所以clone方法是一種原型模式設(shè)計模式糯俗。下面我們來看一下尿褪,克隆方法的使用示例。

//注意此種方式得湘,叫引用復(fù)制杖玲,得到的對象與原來對象一樣,而非創(chuàng)建一個新對象
Resume r = new Resume("張三");
Resume b = r ;

方式二:讓被復(fù)制的對象類繼承接口Cloneable(System已經(jīng)提供了)淘正,并重寫clone()方法摆马。

public class Resume implements Cloneable {
    private String name;
    private String gender;
    private String age;
    private String timeArea;
    private String company;

    public Resume(String name) {
        this.name = name;
    }

    public void setPersonInfo(String gender, String age) {
        this.gender = gender;
        this.age = age;
    }

    public void setWorkExperience(String timeArea, String company) {
        this.timeArea = timeArea;
        this.company = company;
    }

    public void display() {
        System.out.print(name +" "+ gender +" "+age);
        System.out.print("工作經(jīng)歷 "+ timeArea +" "+company);
    }

//只需要調(diào)用此方法就可以實現(xiàn)新簡歷(對象)的生成,且可以再次改新簡歷的細(xì)節(jié)
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return (Resume) super.clone();
    }

}

調(diào)用代碼

Resume resume = Resume("張三豐");
resume.setPersonInfo("男", "30");
resume.setWorkExperience("2010-1018", "xx公司");
Resume cloneResume = (Resume) resume.clone() ; 
cloneResume.setPersonInfo("女", "21");
Resume cloneResume2 = (Resume) resume.clone() ;
cloneResume2.setWorkExperience("2015-2018", "xx公司");

結(jié)果為:

張三豐 男 30
工作經(jīng)歷 2010-1018 xx公司
張三豐 女 21
工作經(jīng)歷 2010-1018 xx公司
張三豐 男 30
工作經(jīng)歷 2015-2018 xx公司

兩種方法都可以實現(xiàn)鸿吆。但是方式一每new一次囤采,就需要執(zhí)行一次構(gòu)造函數(shù),如果構(gòu)造行數(shù)的執(zhí)行時間很長惩淳,那么多次的執(zhí)行初始化操作就太低效率了蕉毯。一般在初始化的信息不發(fā)生變化的情況下,方式二克隆是最好的辦法思犁。這既隱藏了對象的創(chuàng)建細(xì)節(jié)恕刘,又對性能是大大的提高。不用重新初始化對象抒倚,動態(tài)獲得對象運(yùn)行時的狀態(tài)褐着。

三、淺復(fù)制與深復(fù)制

上面的例子托呕,對象的屬性都是String類型含蓉,而String類型是一種擁有值類型特點(diǎn)的特殊引用類型。clone()方法是這樣的项郊,如果字段是值類型的馅扣,則對該字段執(zhí)行逐位復(fù)制,如果字段是引用類型着降,則復(fù)制引用但不復(fù)制引用的對象差油,因此,原始對象及其復(fù)本引用同一對象任洞。以簡歷的例子講蓄喇,假設(shè)WorkExperience作為簡歷類的屬性,那么在克隆后交掏,會把引用對象中的數(shù)據(jù)克隆過來嗎妆偏?

public class Resume implements Cloneable {
    private String name;
    private String gender;
    private String age;
    private WorkExperience experience;

    public Resume(String name) {
        this.name = name;
        experience = new WorkExperience();
    }

    public void setPersonInfo(String gender, String age) {
        this.gender = gender;
        this.age = age;
    }

    public void setWorkExperience(String timeArea, String company) {
        experience.setWorkDate(timeArea);
        experience.setCompany(company);
    }

    public void display() {
        System.out.print(name +" "+ gender +" "+age);
        System.out.print("工作經(jīng)歷 " + experience.getWorkDate() + " " +experience.getCompany());
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return (Resume) super.clone();
    }

}

工作經(jīng)驗類

public class WorkExperience {
    private String workDate;
    private String company;

    public String getWorkDate() {
        return workDate;
    }

    public void setWorkDate(String workDate) {
        this.workDate = workDate;
    }

    public String getCompany() {
        return company;
    }

    public void setCompany(String company) {
        this.company = company;
    }
}

調(diào)用代碼

Resume resume = Resume("張三豐");
resume.setPersonInfo("男", "30");
resume.setWorkExperience("2010-1018", "麥當(dāng)勞公司");

Resume cloneResume = resume.clone() ;
cloneResume.setPersonInfo("男", "22");
cloneResume.setWorkExperience("2017-1018", "肯德基公司");

Resume cloneResume2 = (Resume)resume.clone();
cloneResume2.setPersonInfo("女", "24");
cloneResume2.setWorkExperience("2015-2018", "德克士公司");

resume.display();
cloneResume.display();
cloneResume2.display();

結(jié)果顯示:

張三豐 男 30
工作經(jīng)歷 2015-2018 德克士公司
張三豐 男 22
工作經(jīng)歷 2015-2018 德克士公司
張三豐 女 24
工作經(jīng)歷 2015-2018 德克士公司

并沒有我們想要的結(jié)果,三次顯示都是最后一次設(shè)置的值盅弛。通過查幫助文檔钱骂,我們知道clone()方法是淺表復(fù)制叔锐,對于值類型,沒有什么問題见秽,但對于引用類型愉烙,就只是復(fù)制了引用,還是指向了原來的對象解取,所以就會出現(xiàn)上面的結(jié)果步责,三次結(jié)果相同。
淺復(fù)制肮蛹,被復(fù)制對象的所有變量都含有與原來的對象相同的值勺择,而所有的對其他對象的引用都仍然指向原來的對象创南。但我們可能更需要這樣的一種需求伦忠,把要復(fù)制的對象所引用的對象都復(fù)制一遍。比如剛才的例子稿辙,我們希望是a,b,c三個引用的對象都是不同的昆码,復(fù)制時就一變二,二變?nèi)诖ⅲ藭r赋咽,我們就叫這種方式為‘深復(fù)制’,深復(fù)制把引用對象的變量指向復(fù)制過的新對象吨娜,而不是原有的被引用的對象脓匿。

繼續(xù)改造上面淺復(fù)制為深復(fù)制

public class Resume implements Cloneable {
    private String name;
    private String gender;
    private String age;
    private WorkExperience experience;

    public Resume(String name) {
        this.name = name;
        experience = new WorkExperience();
    }

    private Resume(WorkExperience experience) {
        this.experience = (WorkExperience) experience.clone();
    }

    public void setPersonInfo(String gender, String age) {
        this.gender = gender;
        this.age = age;
    }

    public void setWorkExperience(String timeArea, String company) {
        experience.setWorkDate(timeArea);
        experience.setCompany(company);
    }

    public void display() {
        System.out.print(name +" "+ gender +" "+age);
        System.out.print("工作經(jīng)歷 " + experience.getWorkDate() + " " +experience.getCompany());
    }

    @Override
    protected Object clone(){
        Resume r = new Resume(this.experience);
        r.name = this.name;
        r.gender = this.gender;
        r.age = this.age;
        return r;
    }

}

工作經(jīng)驗類改造,繼承克隆接口宦赠,重寫clone方法

public class WorkExperience implements Cloneable {
    private String workDate;
    private String company;

    public String getWorkDate() {
        return workDate;
    }

    public void setWorkDate(String workDate) {
        this.workDate = workDate;
    }

    public String getCompany() {
        return company;
    }

    public void setCompany(String company) {
        this.company = company;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return (WorkExperience) super.clone();
    }
}

調(diào)用代碼

Resume resume = Resume("張三豐");
resume.setPersonInfo("男", "30");
resume.setWorkExperience("2010-1018", "麥當(dāng)勞公司");

Resume cloneResume = resume.clone() ;
cloneResume.setPersonInfo("男", "22");
cloneResume.setWorkExperience("2017-1018", "肯德基公司");

Resume cloneResume2 = (Resume)resume.clone();
cloneResume2.setPersonInfo("女", "24");
cloneResume2.setWorkExperience("2015-2018", "德克士公司");

resume.display();
cloneResume.display();
cloneResume2.display();

結(jié)果顯示:

張三豐 男 30
工作經(jīng)歷 2010-1018 麥當(dāng)勞公司
張三豐 男 22
工作經(jīng)歷 2017-1018 肯德基公司
張三豐 女 24
工作經(jīng)歷 2015-2018 德克士公司

這就達(dá)到了我們想要的結(jié)果陪毡,三份不同的簡歷。由于在一些特定場合勾扭,會經(jīng)常涉及深復(fù)制或淺復(fù)制毡琉,比如說數(shù)據(jù)集對象DataSet,它就有clone()方法和copy()方法妙色,clone方法用來復(fù)制DataSet的結(jié)構(gòu)桅滋,但不復(fù)制DataSet的數(shù)據(jù),實現(xiàn)了原型模式的淺復(fù)制身辨。copy方法不但復(fù)制結(jié)構(gòu)丐谋,也復(fù)制數(shù)據(jù),其實就是實現(xiàn)了原型模式的深復(fù)制煌珊。

四笋鄙、使用場景:

1、資源優(yōu)化場景怪瓶。
2萧落、類初始化需要消化非常多的資源践美,這個資源包括數(shù)據(jù)、硬件資源等找岖。
3陨倡、性能和安全要求的場景。
4许布、通過 new 產(chǎn)生一個對象需要非常繁瑣的數(shù)據(jù)準(zhǔn)備或訪問權(quán)限兴革,則可以使用原型模式。
5蜜唾、一個對象多個修改者的場景杂曲。
6、一個對象需要提供給其他對象訪問袁余,而且各個調(diào)用者可能都需要修改其值時擎勘,可以考慮使用原型模式拷貝多個對象供調(diào)用者使用。
7颖榜、在實際項目中棚饵,原型模式很少單獨(dú)出現(xiàn),一般是和工廠方法模式一起出現(xiàn)掩完,通過 clone 的方法創(chuàng)建一個對象噪漾,然后由工廠方法提供給調(diào)用者。原型模式已經(jīng)與 Java 融為渾然一體且蓬,大家可以隨手拿來使用欣硼。

五、注意事項:

與通過對一個類進(jìn)行實例化來構(gòu)造新對象不同的是恶阴,原型模式是通過拷貝一個現(xiàn)有對象生成新對象的诈胜。淺拷貝實現(xiàn) Cloneable,重寫clone方法存淫。深復(fù)制是耘斩,采用對象的序列化Serializable技術(shù)進(jìn)行clone,對象的序列化會將對象以及對象的句柄的內(nèi)容都序列化,然后輸出到對象的流中桅咆,可以將流直接保存到內(nèi)存二級制流中括授,然后再用對象輸入流從內(nèi)存二進(jìn)制中讀取該流,再轉(zhuǎn)換成相應(yīng)的對象岩饼,這樣就完成了非常復(fù)雜而且是深層次的復(fù)制荚虚。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市籍茧,隨后出現(xiàn)的幾起案子版述,更是在濱河造成了極大的恐慌,老刑警劉巖寞冯,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件渴析,死亡現(xiàn)場離奇詭異晚伙,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)俭茧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進(jìn)店門咆疗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人母债,你說我怎么就攤上這事午磁。” “怎么了毡们?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵迅皇,是天一觀的道長。 經(jīng)常有香客問我衙熔,道長登颓,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任青责,我火速辦了婚禮挺据,結(jié)果婚禮上取具,老公的妹妹穿的比我還像新娘脖隶。我一直安慰自己,他們只是感情好暇检,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布产阱。 她就那樣靜靜地躺著,像睡著了一般块仆。 火紅的嫁衣襯著肌膚如雪构蹬。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天悔据,我揣著相機(jī)與錄音庄敛,去河邊找鬼。 笑死科汗,一個胖子當(dāng)著我的面吹牛藻烤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播头滔,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼怖亭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了坤检?” 一聲冷哼從身側(cè)響起兴猩,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎早歇,沒想到半個月后倾芝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體讨勤,經(jīng)...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年晨另,在試婚紗的時候發(fā)現(xiàn)自己被綠了悬襟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡拯刁,死狀恐怖脊岳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情垛玻,我是刑警寧澤割捅,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站帚桩,受9級特大地震影響亿驾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜账嚎,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一莫瞬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧郭蕉,春花似錦疼邀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至涨岁,卻和暖如春拐袜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背梢薪。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工蹬铺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人秉撇。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓甜攀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親畜疾。 傳聞我的和親對象是個殘疾皇子赴邻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,492評論 2 348

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

  • 場景 思考一下:克隆技術(shù)是怎么樣的過程? JavaScript語言中的,繼承怎么實現(xiàn)?那里面也有prototype...
    GaaraZ閱讀 1,838評論 0 0
  • 1大同小異的工作周報 Sunny軟件公司一直使用自行開發(fā)的一套OA (Office Automatic,辦公自動化...
    justCode_閱讀 1,151評論 0 3
  • 定義 原型模式屬于對象的創(chuàng)建模式啡捶。通過給出一個原型對象來指明所有創(chuàng)建的對象的類型姥敛,然后用復(fù)制這個原型對象的辦法創(chuàng)建...
    步積閱讀 1,340評論 0 2
  • 從前有個小國家叫杞,杞國有一個人瞎暑,整天胡思亂想彤敛,疑神疑鬼与帆。他一會兒擔(dān)心天會崩塌下來,砸扁了腦袋墨榄;一會兒擔(dān)心地會陷落...
    明荒閱讀 212評論 2 2
  • 春節(jié)玄糟,租女友(男友)回家過年的話題在網(wǎng)上火了。好幾年前就有這樣的話題袄秩,沒想到今年這沒火阵翎。看來現(xiàn)在社會上越來越多的人...
    凱撒2000閱讀 189評論 0 0