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

来源:https://www.cnblogs.com/call-me-devil/archive/2019/07/13/11180403.html
-Advertisement-
Play Games

舉個慄子 問題描述 要求有一個簡歷類,必須要有姓名,可以設置性別和年齡,可以設置工作經歷,最終需要三份簡歷。 簡單實現 簡歷類 測試 測試結果 存在的問題 跟手寫簡歷沒有差別,三份簡歷需要三份實例化,如果客戶需要二十份簡歷,那就得實例化二十次。 原型模式 定義 用原型實例指定創建對象的種類,並且通過 ...


舉個慄子

問題描述

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

簡單實現

簡歷類

/**
 * 簡歷類
 * 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等),則/複製引用/但不/複製引用的對象/;因此,原始對象及其副本引用同一對象。

在此之前,我們先做一個簡單的測試

    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")));

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

true
true
false
true

至於結果為什麼會這樣,網上也許多分析,此處重點在淺複製的講解,因此不做過多敘述。

帶著上面的理解再看下麵的內容。在可克隆的簡歷類例子中,基本數據類型就沒什麼好測試的,有興趣的也可以將年齡改成 int 類型;對於其他 String 類型,就拿 company 欄位來說,我們新建一個測試類看下是什麼結果

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方法是不夠的,它只會對引用類型複製一份引用,那如果連引用類型的欄位也要創建一份新的數據,那便是 “深複製”

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

深複製

此處不糾結於如何對上述 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 的過程。其中複製便分為淺複製深複製


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • BOM BOM ECMAScript是JavaScript的核心,但如果要在Web中使用JavaScript,那麼BOM(瀏覽器對象模型)則無疑才是真正的核心,BOM提供了很多對象,用於訪問瀏覽器的功能,這些功能與任何網頁內容無關,那麼,什麼是BOM呢?我們可以從這幾點解析一下: 1.BOM是Bro ...
  • 實現思路 獲取input的file 使用fileReader() 將圖片轉為base64 使用canvas讀取base64 並降低解析度 把canvas數據轉成blob對象 把blob對象轉file對象 完成壓縮 相關代碼: 最後回調函數中的files可以直接當做正常的input file 使用,如 ...
  • 裝飾器是什麼? 解碼器是將另一段代碼包裝在一個代碼中的簡單方法。 這個概念類似於你以前聽說過的功能成分和高階成分。 這在許多情況下都被使用過,也就是說,成都裝修公司簡單地將一個函數包裝到另一個函數中: 前面的示例生成包裝的新函數,它執行與 DoSomething 相同的操作,但它們的不同之處在於在包 ...
  • HTML代碼: js代碼: ...
  • 可以使用 window.location 獲取當前頁面url。以下是一些簡單應用。 ...
  • html代碼: js代碼: ...
  • 史上最全的,web前端零基礎,系統學習路線圖!學好web前端,前途寬廣!如今隨著“互聯網+”上升到國家戰略,軟體行業與國民經濟關係密,幾乎絕大多數行業的發展都會促進軟體行業的發展。 ...
  • 編譯原理課程設計詞法分析任務書 5)參考文獻: (1)張素琴,呂映芝. 編譯原理[M]., 清華大學出版社 (2)蔣立源、康慕寧等,編譯原理(第2版)[M],西安:西北工業大學出版社 6)課程設計進度安排 1.準備階段(4學時):選擇設計題目、瞭解設計目的要求、查閱相關資料 2.程式模塊設計分析階段 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...