1. 程式人生 > >原型模式 prototype 建立型 設計模式(七)

原型模式 prototype 建立型 設計模式(七)

原型模式  prototype

意圖

用原型例項指定需要建立的物件的型別,然後使用複製這個原型物件的方法創建出更多同類型的物件   顯然,原型模式就是給出一個物件,然後克隆一個或者更多個物件 小時候看過的動畫片《西遊記》,主題曲猴哥中有一句“拔一根毫毛 ,吹出猴萬個 ”這就是原型模式 孫悟空作為原型物件,“拔一根毫毛 ,吹” 這就是呼叫複製物件方法,“猴”萬個,就是結果了,建立了“萬個” 猴子 

原型模式的根本-拷貝

原型模式的根本在於物件的拷貝 說白了就是: 如何複製一個物件?   物件的表示
Object ref = new Object(); 上面這句話可以理解為三個步驟
  1. 建立一個Object型別的引用名為 ref
  2. 建立了一個Object型別的物件
  3. 使變數ref指向這個新的物件的記憶體地址
image_5bf3c33c_7a92   一個物件內部有一個或多個屬性 屬性可能是基本型別也可能是一個引用型別 而對於引用型別就相當於我們上面表述的這種形式 Object ref = new Object();   引用變數指向實際的物件   深拷貝和淺拷貝
拷貝有兩種形式, 淺拷貝和深拷貝 淺拷貝被複制的物件所有的屬性成員變數都含有與原來的物件相同的值 也就是說 如果是引用型別,他仍舊會指向原來的物件,也就是所有的備份中的引用型別指向的是同一個物件 淺拷貝僅僅拷貝當前物件本身 image_5bf3c33c_58c1   深拷貝則恰恰相反, 深拷貝將會拷貝所有的物件 也就是 如果內部有成員變數為引用型別,那麼也會拷貝被指向的物件,不僅僅是拷貝當前物件本身 把 所有被引用到的物件都複製了一遍 image_5bf3c33c_7e15   對於深拷貝,可以藉助於序列化實現,深拷貝一個物件

結構

原型模式作為建立型模式的一種 與工廠模式建造者模式是類似的,都是為了建立物件 只不過是建立的方式不同 原型模式的建立邏輯則是藉助於已經存在的物件,呼叫他的拷貝方法,從而建立相同型別的新的物件 根據依賴倒置原則,面向抽象而不是細節進行程式設計,所以使用抽象角色Prototype用於描述原型類 一種通用的結構描述形式為:
image_5bf3c33c_6de5
  Client 客戶端角色 客戶端程式發起建立物件請求          Prototype 抽象原型角色 抽象角色用於描述原型類,給出具體原型類需要的協議 介面或者抽象類 ConcretePrototype具體原型角色 被複制的物件型別

程式碼示例

package prototype;
public interface Prototype extends Cloneable {
Prototype clone();
}
package prototype;
public class ConcreatePrototype implements Prototype {
    @Override
    public Prototype clone() {
        try{
            return (Prototype)super.clone();
        }catch (CloneNotSupportedException e){
            return null;
        }
    }
}
package prototype;
public class Client {
    public static void main(String[] args){
        Prototype prototype = new ConcreatePrototype();
        Prototype clonedObj = (Prototype)prototype.clone();
        System.out.println(clonedObj.getClass());
        System.out.println(prototype == clonedObj);
    }
}
image_5bf3c33c_60bf

Java天然的原型模式

在Java中, 所有的物件都繼承自Java.lang.Object Object中有clone()方法 ,可以將一個物件進行拷貝 所以說Java天生的內建了原型模式---通過物件的clone方法進行物件的拷貝 不過也有一些具體的規則需要注意   Java語言提供了 Cloneable介面,作為 標記介面 凡是實現了Cloneable介面的類都聲稱:“可以安全的在這個類上使用clone()方法”。 試圖呼叫clone()方法時, 如果此物件的類沒有實現 Cloneable 介面,則會丟擲 CloneNotSupportedException。   clone()方法如下 image_5bf3c33c_5cb7 clone方法是淺拷貝而不是深拷貝 Object中的clone()方法規定了拷貝的一般協議,可以參看API文件  
  1. 對於任何物件 x,表示式:x.clone() != x,克隆物件與原始物件不是同一個物件
  2. x.clone().getClass() == x.getClass() ,克隆物件與原始物件是同一種類型
  3. x.clone().equals(x) 為true
這三點並不是必須的,並不是必須的,並不是必須的,但是除非特殊情況,否則建議應該都滿足    Object 類本身不實現介面 Cloneable 所以,如果在型別為Object的物件上呼叫clone方法,會丟擲異常   Java語言通過Object提供的protected方法以及Cloneable標記介面機制 定義了一套複製物件的工作流程:
  1. 實現Cloneable介面
  2. 覆蓋或者使用繼承而來的clone()方法
對於最簡單的原型模式 的應用,只需要原型類完成這步即可 這就是Java中原型模式型使用方式   因為在Java中,所有的物件直接或者間接地繼承Object 所以始終內建的隱含了Object這一抽象角色   我們的示例程式碼中,Prototype 就是相當於java中的Object   圖中Prototype 為具體的原型類(提供了clone方法的類) image_5bf3c33c_74d8

擁有管理器的原型模式

原型模式中最為關鍵的是呼叫某個物件的拷貝方法,進行原始物件的複製 所以原型模式一種常見的用法就是藉助於這個"原始物件",達到工廠方法的效果 客戶端中儲存一個ConcretePrototype型別的物件 後續的物件建立獲取就是客戶端通過這個內部的物件,呼叫它的拷貝方法進行進一步的操作   如果產品結構比較簡單,可能只需要幾種型別的物件即可 上面的原型結構比較適合,客戶端自己儲存所有的物件 但是 如果產品等級結構比較雜亂,或者說要建立的原型物件是數目並不是固定的 又可以進一步將管理物件的職責進行提取分離,抽象出來一個管理器的角色 專門用於管理這些物件   這種帶管理器的原型模式中,客戶端就不在持有原型物件的引用了,也就是客戶端不在擁有原型物件 取而代之的是通過管理器獲取 image_5bf3c33c_489d Client 客戶端角色 向管理員發起建立物件的請求 Prototype、ConcretePrototype 角色與之前的概念相同 PrototypeManager 角色 原型管理器角色,負責原型物件的建立和管理

示例程式碼

在原來的基礎上增加原型管理器
package prototype;
import java.util.HashMap;
public class PrototypeManager {
    /*hashMap維護原型物件
    * */
    private HashMap<String,Object> map = new HashMap<>();
    /*餓漢式單例模式返回建立原型物件管理器
    邏輯上原型物件管理器只有一個
    * */
    private static PrototypeManager prototypeManager= new PrototypeManager();
    public static PrototypeManager getPm(){
        return prototypeManager;
    }
    /*初始化內建兩個原型物件
    * */
    private PrototypeManager(){
        map.put("product1",new ConcreatePrototype());
        map.put("product2",new ConcreatePrototype());
    }
    /*提供了新增原型物件方法*/
    public void add(String key,Prototype prototype){
        map.put(key,prototype);
    }
    /*提供了獲取物件的方法,獲取的物件依賴的是clone,而不是儲存進去的物件*/
    public Prototype get(String key){
        return ((Prototype)map.get(key)).clone();
    }
}
測試類Client角色中也增加相關程式碼 image_5bf3c33c_7ec3 看得出來,從物件管理器中獲取的物件,都是原有物件的一個clone  並不是相同的物件 帶管理器的原型模式也叫做 登記形式的原型模式 登記就是相當於在管理器中備案

適用場景

為什麼要使用原型模式? 簡簡單單的new一個物件不好麼?為什麼非要複製呢?   當建立新的物件的過程較為複雜時,使用原型模式可以簡化物件的建立過程 比如初始化佔用時間較長,這種情況下建立一個物件將不再簡單,所以考慮原型模式   對於工廠模式中,工廠需要有與產品等級結構相同的結構 一個工廠生產一種產品 然而,如果產品的等級結構容易發生變化的話,工廠類的等級結構可能不得不進行變化 也就是說對於產品等級結構容易變化的場景來說,工廠模式將會不方便 如果採用 原型模式,那麼就不再需要關注產品的等級結構,產品的等級結構可以隨意變動 因為原型模式僅僅關注具體的產品物件,物件之間的結構以及結構的變化並不會產生影響 所以在 這種情況下,原型模式擁有比工廠模式更為靈活,擴充套件性更好 不過 代價是每個類中都必須有一個clone方法,物件的建立邏輯從每個工廠轉移到了每個clone方法中   在框架中使用原型模式可以與生成的例項進行解耦 框架中面向抽象進行程式設計,只關注他的抽象型別,不關注他的具體型別 具體的物件可以通過配置檔案等方式注入 框架藉助於原型模式可以獲得這種型別的物件,而完全不用關注這個型別 否則當你使用某種型別時,你必然需要建立物件,也就是始終要接觸到具體的型別 這種方法就可以讓你永遠不知道具體的型別,徹底的分離

總結

原型模式的根本在於複製,所以依賴拷貝方法,java也內建了這種模式 在java中使用時,只需要實現Cloneable介面並且重寫或者使用繼承而來的clone方法即可 原型模式可以很好地隱藏建立物件的具體型別 當建立一個物件變得複雜時,我們可以考慮使用原型模式 通過複製的方式簡化物件的建立過程 但是這有一個前提,那就是複製物件相對比較簡單 但是,但是,但是,有的時候,複製一個物件本身卻也是非常複雜的,一般可以藉助於序列化來進行 而且,每一個類都需要擁有clone方法,當需要對已有的類進行擴充套件改造時,clone方法也需要進行修改 這並不複合開閉原則