重溫23種設計模式(11):原型模式

帶你聊技術發表於2023-11-21

來源:mikechen的網際網路架構

為什麼要學設計模式?設計模式有哪些優點?

  • 提升檢視框架原始碼能力
  • 提升對複雜業務的程式碼設計能力以及 code 能力
  • 為今後的面試以及進階之路夯實基礎

今天我們要講的是設計模式中的原型模式(Prototype Pattern)

原型模式提供了建立物件的最佳方式,主要解決物件複製的問題,適用於建立複雜物件圖、實現物件的快照和恢復等場景中

重溫23種設計模式(11):原型模式


01
  原型模式的定義

原型模式透過克隆現有物件來建立新物件,避免了頻繁的物件例項化過程,屬於建立型模式。

簡單理解,就是定義一個原型物件作為建立其他物件的基礎,透過克隆原型物件,我們可以建立多個具有相同屬性和行為的新物件。

例如:

孫悟空有個十分牛逼的絕活兒,叫分身術,可以幻化出多個相同的孫悟空。

這個幻化出的新的分身,和設計模式中的原型模式是相似的。

重溫23種設計模式(11):原型模式

02
  原型模式的 UML 類圖

重溫23種設計模式(11):原型模式

原型模式中的 3 個重要角色:

  • 客戶端(Client):使用具體原型類中的 clone() 方法,來複制新的物件。


  • 抽象原型(Prototype):可以是抽象類或介面,規定了具體原型物件必須實現的 clone() 方法。


  • 具體原型類(ConcretePrototype):實現抽象原型類的 clone() 方法,它是可被複制的物件。


Prototype 通常不用自己定義,因為複製這個操作十分常用。

在 Java 中,提供了Cloneable 介面來支援複製操作,它就是原型模式中的 Prototype 。

當然了,原型模式也未必非得去實現 Cloneable 介面,也有其他的實現方式。

03
  原型模式的兩種實現

原型模式有兩種複製方式:淺複製深複製

1)淺複製

建立一個新物件,新物件的屬性和原來物件完全相同,對於非基本型別屬性,仍指向原有屬性所指向的物件的記憶體地址。

簡單理解,就是只複製所考慮的物件,而不復制它所引用的物件

Object 類提供的方法 clone ,只是複製本物件 , 其物件內部的陣列、引用物件等都不複製。

2)深複製

建立一個新物件,屬性中引用的其他物件也會被克隆,不再指向原有物件地址。

簡單理解,就是複製一切,把要複製的物件所引用的物件都複製了一遍

相比於淺複製,深複製速度慢並且開銷大,但是複製前後兩個物件互不影響。

04
  原型模式的實現示例

原始碼示例:

將名片複製到自己的名片庫中。

我們先實現名片類。

具體的原型類:




























public class BusinessCard implements Cloneable {    private String name;    private String company;    public BusinessCard(){        System.out.println("執行建構函式BusinessCard");    }    public void setName(String name) {        this.name = name;    }    public void setCompany(String company) {        this.company = company;    }    @Override    public BusinessCard clone() {        BusinessCard businessCard = null;        try {            businessCard = (BusinessCard) super.clone();        } catch (CloneNotSupportedException e) {            e.printStackTrace();        }        return businessCard;    }    public void show() {        System.out.println("name:" + name);        System.out.println("company:" + company);    }}


BusinessCard 類實現了 Cloneable 介面,它是一個標識介面,表示這個物件是可複製的。

只要重寫 clone 方法,就可以實現複製。如果實現了 Cloneable 介面、卻沒有重寫 clone 方法,就會報錯。

需要注意的是:

clone 方法不是在 Cloneable 介面中定義的(Cloneable 介面中沒有定義任何方法),而是在 Object 中定義的。

客戶端呼叫:




















public class Client {    public static void main(String[] args) {        BusinessCard businessCard = new BusinessCard();        businessCard.setName("錢三");        businessCard.setCompany("阿里");        //複製名片        BusinessCard cloneCard1 = businessCard.clone();        cloneCard1.setName("趙四");        cloneCard1.setCompany("百度");
       BusinessCard cloneCard2 = businessCard.clone();        cloneCard2.setName("孫五");        cloneCard2.setCompany("騰訊");
       businessCard.show();        cloneCard1.show();        cloneCard2.show();    }}


除了第一個名片,其他兩個名片都是透過 clone 方法得到的。

但是,clone 方法並不會執行 cloneCard1 和 cloneCard2 的建構函式。

執行結果:








執行建構函式 BusinessCardname:錢三company:阿里name:趙四company:百度name:孫五company:騰訊



1)實現淺複製


上述的例子中,BusinessCard 的欄位都是 String 型別的,如果欄位是引用的型別的,會出現什麼情況呢?
































public class DeepBusinessCard implements Cloneable {    private String name;    private Company company = new Company();
   public void setName(String name) {        this.name = name;    }
   public void setCompany(String name, String address) {        this.company.setName(name);        this.company.setAddress(address);    }
   @Override    public DeepBusinessCard clone() {        DeepBusinessCard businessCard = null;        try {            businessCard = (DeepBusinessCard) super.clone();        } catch (CloneNotSupportedException e) {            e.printStackTrace();        }        return businessCard;    }
   public void show() {        System.out.println("name:" + name);        System.out.println("company:" + company.getName() + "-address-" + company.getAddress());    }
}

我們定義了 DeepBusinessCard 類,它的欄位 company 是引用型別的。

Company 類:


















public class Company {    private String name;    private String address;
   public String getAddress() {        return address;    }    public void setAddress(String address) {        this.address = address;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }


在客戶端使用 DeepBusinessCard :




















public class Client {    public static void main(String[] args) {       DeepBusinessCard businessCard=new DeepBusinessCard();        businessCard.setName("錢三");        businessCard.setCompany("阿里","北京望京");
       DeepBusinessCard cloneCard1=businessCard.clone();        cloneCard1.setName("趙四");        cloneCard1.setCompany("百度","北京西二旗");
       DeepBusinessCard cloneCard2=businessCard.clone();        cloneCard2.setName("孫五");        cloneCard2.setCompany("騰訊","北京中關村");
       businessCard.show();        cloneCard1.show();        cloneCard2.show();    }}


執行結果:







name:錢三company:騰訊-address-北京中關村name:趙四company:騰訊-address-北京中關村name:孫五company:騰訊-address-北京中關村


Object 類提供的 clone 方法,不會複製物件中的內部陣列和引用物件,導致它們仍舊指向原來物件的內部元素地址。

因此,company 欄位為最後設定的”騰訊”、”北京中關村”。

這種複製方式,我們就稱為淺複製

company 欄位是引用型別,businessCard 被複製後,company 欄位仍舊指向原來的 businessCard 物件的 company 欄位的地址。

我們每次設定 company 欄位,都會覆蓋上一次設定的值,最終留下的就是最後一次設定的值:騰訊、北京中關村。

淺複製引用關係:

重溫23種設計模式(11):原型模式

有多個物件可以修改 company ,顯然,這樣的引用關係是不符合需求的。

為了解決這個問題,我們引入了深複製模式

修改引用關係為深複製:

重溫23種設計模式(11):原型模式

複製 businessCard 物件的同時,也將它內部的引用物件 company 進行複製,使得每個複製的物件之間無任何關聯,都指向了自身對應的 company。

這種複製方式,我們就稱為深複製

深複製模式把要複製的物件所引用的物件都複製了一遍。

2)實現深複製


首先需要修改 Company 類:















public class Company implements Cloneable{    private String name;    private String address;    ...    public Company clone(){        Company company=null;        try {            company= (Company) super.clone();        } catch (CloneNotSupportedException e) {            e.printStackTrace();        }        return company;    }}


為了實現 Company 類能被複製,Company 類也需要實現 Cloneable 介面、並且覆寫 clone 方法。

接著,修改 DeepBusinessCard 的 clone 方法:



















public class DeepBusinessCard implements Cloneable {    private String name;    private Company company = new Company();    ...    @Override    public DeepBusinessCard clone() {        DeepBusinessCard businessCard = null;        try {            businessCard = (DeepBusinessCard) super.clone();            businessCard.company = this.company.clone();//1
       } catch (CloneNotSupportedException e) {            e.printStackTrace();        }        return businessCard;    }  ...}


註釋 1 增加了對 company 欄位的複製處理,最後在客戶端呼叫。

輸出結果:







name:錢三company:阿里-address-北京望京name:趙四company:百度-address-北京西二旗name:孫五company:騰訊-address-北京中關村


可見,採用深複製模式修改一個物件的引用型別的成員時,不會再影響另外物件的該成員。

05
  原型模式的優缺點

優點:

  • 克隆物件時,對客戶端隱藏實現細節,避免了程式碼耦合。

  • 逃避建構函式的約束。

  • 可以動態增加或減少產品類。

  • 原型模式提供了簡化的建立結構。


缺點:

  • 必須實現 Cloneable 介面;

  • clone 方法位於類的內部,當對已有類進行改造的時候,需要修改程式碼,違背了開閉原則

  • 在實現深複製時,需要編寫較為複雜的程式碼。

 

06
  原型模式的使用場景

不管是複雜還是簡單的物件,只要存在物件複製的場景,都適合使用原型模式。

  • 類的初始化需要耗費較多的資源時。

  • 透過 new 產生一個物件、需要非常繁瑣的資料準備或訪問許可權時。

  • 一個物件需要提供給其他物件訪問,而且各個呼叫者可能都需要修改其值時。

 

07
  原型模式與工廠方法模式的區別

工廠方法模式是一種建立型設計模式,它提供了一種在不指定具體類的情況下建立物件的方法。

關於工廠方法模式,看這篇:【重溫23種設計模式】之工廠方法模式

原型模式與工廠方法模式的主要區別是:物件建立的方式

原型模式透過複製現有的物件來建立新物件,適用於以下場景:

  • 需要建立大量具有相似屬性的物件。

  • 物件的建立成本很高,例如需要執行復雜的初始化邏輯。

  • 需要在執行時動態地建立和修改物件。


工廠方法模式透過呼叫工廠方法來建立新物件,適用於以下場景:

  • 需要建立具有不同型別或不同實現的物件。

  • 物件建立邏輯較為複雜,不適合在客戶端程式碼中直接例項化。

  • 需要將物件建立過程與客戶端程式碼解耦。

 

08
  原型模式與單例模式的區別

單例模式是一種建立型設計模式,它確保一個類只有一個例項,並提供一個全域性訪問點。


關於單例模式,看這篇:8大單例模式實現方式總結


原型模式與單例模式的主要區別在於它們的目的


原型模式用於透過複製現有物件來建立新物件,適用於以下場景:


  • 需要建立大量具有相似屬性的物件。

  • 物件的建立成本很高,例如需要執行復雜的初始化邏輯。

  • 需要在執行時動態地建立和修改物件。


單例模式用於確保一個類只有一個例項,適用於以下場景:

    • 需要確保一個類只有一個例項。

    • 需要全域性訪問點以方便地訪問該例項。

    • 需要全域性共享資源或配置資訊。



總結
 

透過本文,我們瞭解並掌握了原型模式的概念、原理、應用場景、優缺點、實現方式等。

原型模式是一種強大而靈活的設計模式,它可以簡化物件的建立過程、提高效能,具備更好的可維護性。

在實現原型模式時,需要根據具體需求選擇使用深複製或淺複製。原型模式適用於在處理物件建立、動態修改物件結構、高效能快取等場景中。

原型

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024922/viewspace-2996287/,如需轉載,請註明出處,否則將追究法律責任。