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

一、什么是原型模式

原型模式(Prototype),用原型實(shí)例指定創(chuàng)建對(duì)象的種類,并且通過(guò)拷貝這些原型創(chuàng)建新的對(duì)象。簡(jiǎn)單說(shuō),就是從一個(gè)對(duì)象再創(chuàng)建另外一個(gè)可定制的對(duì)象,而且不需要知道任何創(chuàng)建細(xì)節(jié)。

二、原型的引入克隆

現(xiàn)在我們有一個(gè)需求,要求有一個(gè)簡(jiǎn)歷類,必須要有姓名,可以設(shè)置性別和年齡,可以設(shè)置工作經(jīng)歷。最終我們需要寫(xiě)三份簡(jiǎn)歷。

通常,在不用原型模式的情況下,是這樣實(shí)現(xiàn)的。方式一:
簡(jiǎ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份簡(jiǎn)歷需要三次實(shí)例化,這樣是不是有點(diǎn)麻煩,如果需要100份,那我們就要實(shí)例化100次。一旦寫(xiě)錯(cuò)了一個(gè)字,都要改100次。
當(dāng)然我們想偷懶一下,可以這樣寫(xiě)。

    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)建對(duì)象是通過(guò)new方式創(chuàng)建的,但是在我們熟知的概念中,還有一種創(chuàng)建對(duì)象的方式是克隆方法。顧名思義,是通過(guò)已有的對(duì)象復(fù)制一個(gè)新的對(duì)象一模一樣的。即我們上面原型模式的定義,所以clone方法是一種原型模式設(shè)計(jì)模式。下面我們來(lái)看一下,克隆方法的使用示例。

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

方式二:讓被復(fù)制的對(duì)象類繼承接口Cloneable(System已經(jīng)提供了),并重寫(xiě)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)用此方法就可以實(shí)現(xiàn)新簡(jiǎn)歷(對(duì)象)的生成,且可以再次改新簡(jiǎ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公司

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

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

上面的例子,對(duì)象的屬性都是String類型,而String類型是一種擁有值類型特點(diǎn)的特殊引用類型。clone()方法是這樣的,如果字段是值類型的,則對(duì)該字段執(zhí)行逐位復(fù)制,如果字段是引用類型,則復(fù)制引用但不復(fù)制引用的對(duì)象,因此,原始對(duì)象及其復(fù)本引用同一對(duì)象。以簡(jiǎn)歷的例子講,假設(shè)WorkExperience作為簡(jiǎn)歷類的屬性,那么在克隆后,會(huì)把引用對(duì)象中的數(shù)據(jù)克隆過(guò)來(lái)嗎?

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)驗(yàn)類

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 德克士公司

并沒(méi)有我們想要的結(jié)果,三次顯示都是最后一次設(shè)置的值。通過(guò)查幫助文檔,我們知道clone()方法是淺表復(fù)制,對(duì)于值類型,沒(méi)有什么問(wèn)題,但對(duì)于引用類型,就只是復(fù)制了引用,還是指向了原來(lái)的對(duì)象,所以就會(huì)出現(xiàn)上面的結(jié)果,三次結(jié)果相同。
淺復(fù)制,被復(fù)制對(duì)象的所有變量都含有與原來(lái)的對(duì)象相同的值,而所有的對(duì)其他對(duì)象的引用都仍然指向原來(lái)的對(duì)象。但我們可能更需要這樣的一種需求,把要復(fù)制的對(duì)象所引用的對(duì)象都復(fù)制一遍。比如剛才的例子,我們希望是a,b,c三個(gè)引用的對(duì)象都是不同的,復(fù)制時(shí)就一變二,二變?nèi)藭r(shí),我們就叫這種方式為‘深復(fù)制’,深復(fù)制把引用對(duì)象的變量指向復(fù)制過(guò)的新對(duì)象,而不是原有的被引用的對(duì)象。

繼續(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)驗(yàn)類改造,繼承克隆接口,重寫(xiě)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é)果,三份不同的簡(jiǎn)歷。由于在一些特定場(chǎng)合,會(huì)經(jīng)常涉及深復(fù)制或淺復(fù)制,比如說(shuō)數(shù)據(jù)集對(duì)象DataSet,它就有clone()方法和copy()方法,clone方法用來(lái)復(fù)制DataSet的結(jié)構(gòu),但不復(fù)制DataSet的數(shù)據(jù),實(shí)現(xiàn)了原型模式的淺復(fù)制。copy方法不但復(fù)制結(jié)構(gòu),也復(fù)制數(shù)據(jù),其實(shí)就是實(shí)現(xiàn)了原型模式的深復(fù)制。

四、使用場(chǎng)景:

1、資源優(yōu)化場(chǎng)景。
2、類初始化需要消化非常多的資源,這個(gè)資源包括數(shù)據(jù)、硬件資源等。
3、性能和安全要求的場(chǎng)景。
4、通過(guò) new 產(chǎn)生一個(gè)對(duì)象需要非常繁瑣的數(shù)據(jù)準(zhǔn)備或訪問(wèn)權(quán)限,則可以使用原型模式。
5、一個(gè)對(duì)象多個(gè)修改者的場(chǎng)景。
6、一個(gè)對(duì)象需要提供給其他對(duì)象訪問(wèn),而且各個(gè)調(diào)用者可能都需要修改其值時(shí),可以考慮使用原型模式拷貝多個(gè)對(duì)象供調(diào)用者使用。
7、在實(shí)際項(xiàng)目中,原型模式很少單獨(dú)出現(xiàn),一般是和工廠方法模式一起出現(xiàn),通過(guò) clone 的方法創(chuàng)建一個(gè)對(duì)象,然后由工廠方法提供給調(diào)用者。原型模式已經(jīng)與 Java 融為渾然一體,大家可以隨手拿來(lái)使用。

五、注意事項(xiàng):

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 場(chǎng)景 思考一下:克隆技術(shù)是怎么樣的過(guò)程? JavaScript語(yǔ)言中的,繼承怎么實(shí)現(xiàn)?那里面也有prototype...
    GaaraZ閱讀 1,906評(píng)論 0 0
  • 1大同小異的工作周報(bào) Sunny軟件公司一直使用自行開(kāi)發(fā)的一套OA (Office Automatic,辦公自動(dòng)化...
    justCode_閱讀 1,240評(píng)論 0 3
  • 定義 原型模式屬于對(duì)象的創(chuàng)建模式。通過(guò)給出一個(gè)原型對(duì)象來(lái)指明所有創(chuàng)建的對(duì)象的類型,然后用復(fù)制這個(gè)原型對(duì)象的辦法創(chuàng)建...
    步積閱讀 1,622評(píng)論 0 2
  • 從前有個(gè)小國(guó)家叫杞,杞國(guó)有一個(gè)人,整天胡思亂想,疑神疑鬼。他一會(huì)兒擔(dān)心天會(huì)崩塌下來(lái),砸扁了腦袋;一會(huì)兒擔(dān)心地會(huì)陷落...
    明荒閱讀 247評(píng)論 2 2
  • 春節(jié),租女友(男友)回家過(guò)年的話題在網(wǎng)上火了。好幾年前就有這樣的話題,沒(méi)想到今年這沒(méi)火??磥?lái)現(xiàn)在社會(huì)上越來(lái)越多的人...
    凱撒2000閱讀 275評(píng)論 0 0

友情鏈接更多精彩內(nèi)容