大話設計模式筆記(七)の原型模式

舉個栗子

問題描述

要求有一個簡歷類,必需要有姓名,能夠設置性別和年齡,能夠設置工做經歷,最終須要三份簡歷。ide

簡單實現

簡歷類

/**
 * 簡歷類
 * Created by callmeDevil on 2019/7/13.
 */
public class Resume {
    
    private String name;
    private String sex;
    private String age;
    private String timeArea;
    private String company;
    
    public Resume (String name) {
        this.name = name;
    }

    /**
     * 設置我的信息
     * @param sex
     * @param age
     */
    public void setPersonalInfo(String sex, String age){
        this.sex = sex;
        this.age = age;
    }

    /**
     * 設置工做經歷
     * @param timeArea
     * @param company
     */
    public void setWorkExperience(String timeArea, String company) {
        this.timeArea = timeArea;
        this.company = company;
    }

    /**
     * 顯示
     */
    public void display () {
        System.out.println(String.format("%s %s %s", name, sex, age));
        System.out.println(String.format("工做經歷:%s %s", timeArea, company));
    }
 
    // 此處省略get、set方法
    
}

測試

/**
 * 測試
 * Created by callmeDevil on 2019/7/13.
 */
public class Test {

    public static void main(String[] args) {
        Resume resumeA = new Resume("callmeDevil");
        resumeA.setPersonalInfo("男", "24");
        resumeA.setWorkExperience("2018-2019", "偉大的航道");

        Resume resumeB = new Resume("callmeDevil");
        resumeB.setPersonalInfo("男", "24");
        resumeB.setWorkExperience("2018-2019", "偉大的航道");

        Resume resumeC = new Resume("callmeDevil");
        resumeC.setPersonalInfo("男", "24");
        resumeC.setWorkExperience("2018-2019", "偉大的航道");

        resumeA.display();
        resumeB.display();
        resumeC.display();
    }

}

測試結果

callmeDevil 男 24
工做經歷:2018-2019 偉大的航道
callmeDevil 男 24
工做經歷:2018-2019 偉大的航道
callmeDevil 男 24
工做經歷:2018-2019 偉大的航道

存在的問題

跟手寫簡歷沒有差異,三份簡歷須要三份實例化,若是客戶須要二十份簡歷,那就得實例化二十次。性能

原型模式

定義

用原型實例指定建立對象的種類,而且經過拷貝這些原型建立新的對象。測試

UML圖

代碼實現

/**
 * 簡歷類(實現JDK克隆接口)
 * Created by callmeDevil on 2019/7/13.
 */
public class Resume implements Cloneable{

    private String name;
    private String sex;
    private String age;
    private String timeArea;
    private String company;

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

    /**
     * 設置我的信息
     * @param sex
     * @param age
     */
    public void setPersonalInfo(String sex, String age){
        this.sex = sex;
        this.age = age;
    }

    /**
     * 設置工做經歷
     * @param timeArea
     * @param company
     */
    public void setWorkExperience(String timeArea, String company) {
        this.timeArea = timeArea;
        this.company = company;
    }

    /**
     * 顯示
     */
    public void display () {
        System.out.println(String.format("%s %s %s", name, sex, age));
        System.out.println(String.format("工做經歷:%s %s", timeArea, company));
    }

    /**
     * 實現克隆方法,可進行本身的克隆邏輯
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    // 此處省略get、set方法

}

測試

/**
 * 原型模式測試
 * Created by callmeDevil on 2019/7/13.
 */
public class Test {

    public static void main(String[] args) throws CloneNotSupportedException {
        Resume resumeA = new Resume("callmeDevil");
        resumeA.setPersonalInfo("男", "24");
        resumeA.setWorkExperience("2018-2019", "偉大的航道");

        // 只須要調用clone方法就能夠實現新簡歷的生成,而且能夠修改新簡歷的細節
        Resume resumeB = (Resume) resumeA.clone();
        resumeB.setWorkExperience("2019-2020", "新世界");

        Resume resumeC = (Resume) resumeA.clone();
        resumeC.setPersonalInfo("男", "25");

        resumeA.display();
        resumeB.display();
        resumeC.display();
    }

}

測試結果

callmeDevil 男 24
工做經歷:2018-2019 偉大的航道
callmeDevil 男 24
工做經歷:2019-2020 新世界
callmeDevil 男 25
工做經歷:2018-2019 偉大的航道

好處

  • 通常在初始化的信息不發生變化的狀況下,克隆是最好的方法。這既隱藏了對象建立的細節,又對性能是大大的提升。
  • 不用從新初始化對象,而是動態的得到對象運行時的狀態。

淺複製與深複製

淺複製

在上面這個簡歷類中,若是字段是值類型(基本數據類型)的,則對該字段直接進行復制;若是是引用類型(String等),則/複製引用/但不/複製引用的對象/;所以,原始對象及其副本引用同一對象。this

在此以前,咱們先作一個簡單的測試code

System.out.println("123" == "123");
    System.out.println("123".equals("123"));
    System.out.println(new String("123") == new String("123"));
    System.out.println(new String("123").equals(new String("123")));

相信有點基礎的都知道答案吧?就不賣弄了,直接上結果orm

true
true
false
true

至於結果爲何會這樣,網上也許多分析,此處重點在淺複製的講解,所以不作過多敘述。對象

帶着上面的理解再看下面的內容。在可克隆的簡歷類例子中,基本數據類型就沒什麼好測試的,有興趣的也能夠將年齡改爲 int 類型;對於其餘 String 類型,就拿 company 字段來講,咱們新建一個測試類看下是什麼結果blog

public class Test2 {

    public static void main(String[] args) throws CloneNotSupportedException {
        Resume resumeA = new Resume("callmeDevil");
        resumeA.setWorkExperience("0", "偉大的航道");// 直接聲明String

        Resume resumeB = (Resume) resumeA.clone();

        // A 與 B 的公司兩種比較
        System.out.println(resumeA.getCompany() == resumeB.getCompany());
        System.out.println(resumeA.getCompany().equals(resumeB.getCompany()));

        resumeA.setWorkExperience("0", new String("偉大的航道"));//new 的方式建立String

        Resume resumeC = (Resume) resumeA.clone();

        // A 與 C 的公司兩種比較
        System.out.println(resumeA.getCompany() == resumeC.getCompany());
        System.out.println(resumeA.getCompany().equals(resumeC.getCompany()));
    }

}

比對第一個「123」的例子,稍微思考下在比對下面結果看是否一致接口

true
true
true
true

是的,這就是淺複製。不論A簡歷company直接聲明的仍是 new 出來的,在clone方法中都只會對 company 字段複製一份引用而已。所以纔會出現 「==」「equal()」的結果都是「true」。對於引用類型來講,這個字段所在類實現了clone方法是不夠的,它只會對引用類型複製一份引用,那若是連引用類型的字段也要建立一份新的數據,那即是 「深複製」get

  • 淺複製就是,被複制的對象的全部變量都含有與原來對象相同的值,而全部其餘對象的引用都仍然只想原來的對象。
  • 深複製把引用對象的變量指向複製過的新對象,而不是援用的被引用的對象。

深複製

此處不糾結於如何對上述 String 的深複製。現將簡歷類進行改造,把「工做經歷」抽出成另外一個類 WorkExperience

簡歷類2

/**
 * 簡歷類2(深複製)
 * Created by callmeDevil on 2019/7/13.
 */
public class Resume2 implements Cloneable {

    private String name;
    private String sex;
    private String age;
    // 工做經歷
    private WorkExperience work;

    public Resume2 (String name) {
        this.name = name;
        this.work = new WorkExperience();
    }
    
    private Resume2(WorkExperience work) throws CloneNotSupportedException {
        this.work = (WorkExperience) work.clone();
    }

    /**
     * 設置我的信息
     * @param sex
     * @param age
     */
    public void setPersonalInfo(String sex, String age){
        this.sex = sex;
        this.age = age;
    }

    /**
     * 設置工做經歷
     * @param timeArea
     * @param company
     */
    public void setWorkExperience(String timeArea, String company) {
        // 此處賦值給 work 對象
        this.work.setWorkDate(timeArea);
        this.work.setCompany(company);
    }

    /**
     * 顯示
     */
    public void display () {
        System.out.println(String.format("%s %s %s", name, sex, age));
        // 此處顯示 work 對象的值
        System.out.println(String.format("工做經歷:%s %s", work.getWorkDate(), work.getCompany()));
    }

    /**
     * 實現克隆方法,可進行本身的克隆邏輯
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        // 調用私有的構造方法,讓「工做經歷」對象克隆完成,而後再給這個「簡歷」對象
        // 相關的字段賦值,最終返回一個深複製的簡歷對象
        Resume2 obj = new Resume2(this.work);
        obj.setName(this.name);
        obj.setSex(this.sex);
        obj.setAge(this.age);
        return obj;
    }

    // 此處省略get、set方法
    
}

工做經歷

/**
 * 工做經歷
 * Created by callmeDevil on 2019/7/13.
 */
public class WorkExperience implements Cloneable{

    private String workDate;

    private String company;

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

    // 此處省略get、set方法
    
}

測試類

/**
 * 深複製測試
 * Created by callmeDevil on 2019/7/13.
 */
public class TestDeepClone {

    public static void main(String[] args) throws CloneNotSupportedException {
        Resume2 resumeA = new Resume2("callmeDevil");
        resumeA.setPersonalInfo("男", "24");
        resumeA.setWorkExperience("2018-2019", "偉大的航道");

        Resume2 resumeB = (Resume2) resumeA.clone();
        resumeB.setWorkExperience("2019-2020", "新世界");

        Resume2 resumeC = (Resume2) resumeA.clone();
        resumeC.setWorkExperience("2020-XXXX", "木葉忍者村");

        resumeA.display();
        resumeB.display();
        resumeC.display();
    }

}

測試結果

callmeDevil 男 24
工做經歷:2018-2019 偉大的航道
callmeDevil 男 24
工做經歷:2019-2020 新世界
callmeDevil 男 24
工做經歷:2020-XXXX 木葉忍者村

能夠看到,每次克隆都能將簡歷類中的工做經歷類一同新建,而不是單純的同個對象進行改變內容。若是是淺複製的實現,那麼在相同測試類中會出現什麼結果呢?應該能猜到吧,那就是三次輸出都是同樣的。
對於工做經歷類淺複製實現本文就不描述了,有興趣的能夠自行測試,很簡單,須要修改的地方有這麼兩處:

  • 簡歷類實現的克隆方法中直接調用 super.clone() 而不須要重寫。
  • 取消工做經歷類實現克隆接口及其方法。

只須要更改這兩處,在相同的測試類中就能看到如下結果

callmeDevil 男 24
工做經歷:2020-XXXX 木葉忍者村
callmeDevil 男 24
工做經歷:2020-XXXX 木葉忍者村
callmeDevil 男 24
工做經歷:2020-XXXX 木葉忍者村

看到此處就不須要解釋了吧,每次克隆只是新實例化了「簡歷」,但三個「簡歷」中的「工做經歷」都是同一個,每次 set值 的時候都必將影響到全部對象,因此輸出的工做經歷都是相同的。這也同時說明了實現深複製的必要條件

  • 須要實現深複製的引用類型字段的類(好比上文中的工做經歷)必須實現 Cloneable 接口
  • 該字段的所屬類(好比上文中的簡歷)實現的克隆方法中必須對該字段進行克隆與賦值

作到以上兩點,便可將本來淺複製的功能轉變爲深複製

總結

原型模式無非就是對指定建立的原型實例進行復制,再對新對象另作操做,省去從新 new 的過程。其中複製便分爲淺複製深複製

相關文章
相關標籤/搜索