原型模式 prototype
意圖
用原型實例指定需要創建的對象的類型,然后使用復制這個原型對象的方法創建出更多同類型的對象
顯然,原型模式就是給出一個對象,然后克隆一個或者更多個對象
小時候看過的動畫片《西游記》,主題曲猴哥中有一句“拔一根毫毛 ,吹出猴萬個 ”這就是原型模式
孫悟空作為原型對象,“拔一根毫毛 ,吹” 這就是調用復制對象方法,“猴”萬個,就是結果了,創建了“萬個” 猴子
原型模式的根本-拷貝
原型模式的根本在於對象的拷貝
說白了就是:
如何復制一個對象?
對象的表示
Object ref = new Object();
上面這句話可以理解為三個步驟
- 創建一個Object類型的引用名為 ref
- 創建了一個Object類型的對象
- 使變量ref指向這個新的對象的內存地址
一個對象內部有一個或多個屬性
屬性可能是基本類型也可能是一個引用類型
而對於引用類型就相當於我們上面表述的這種形式
Object ref = new Object();
引用變量指向實際的對象
深拷貝和淺拷貝
拷貝有兩種形式,
淺拷貝和深拷貝
淺拷貝被復制的對象所有的屬性成員變量都含有與原來的對象相同的值
也就是說
如果是引用類型,他仍舊會指向原來的對象,也就是所有的備份中的引用類型指向的是同一個對象
淺拷貝僅僅拷貝當前對象本身
深拷貝則恰恰相反,
深拷貝將會拷貝所有的對象
也就是
如果內部有成員變量為引用類型,那么也會拷貝被指向的對象,不僅僅是拷貝當前對象本身
把
所有被引用到的對象都復制了一遍
對於深拷貝,可以借助於序列化實現,深拷貝一個對象
結構
原型模式作為創建型模式的一種
與工廠模式建造者模式是類似的,都是為了創建對象
只不過是創建的方式不同
原型模式的創建邏輯則是借助於已經存在的對象,調用他的拷貝方法,從而創建相同類型的新的對象
根據依賴倒置原則,面向抽象而不是細節進行編程,所以使用抽象角色Prototype用於描述原型類
一種通用的結構描述形式為:
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); } }
Java天然的原型模式
在Java中,
所有的對象都繼承自Java.lang.Object
而
Object中有clone()方法 ,可以將一個對象進行拷貝
所以說Java天生的內置了原型模式---通過對象的clone方法進行對象的拷貝
不過也有一些具體的規則需要注意
Java語言提供了
Cloneable接口,作為
標記接口
凡是實現了Cloneable接口的類都聲稱:“可以安全的在這個類上使用clone()方法”。
試圖調用clone()方法時,
如果此對象的類沒有實現 Cloneable 接口,則會拋出 CloneNotSupportedException。
clone()方法如下
clone方法是淺拷貝而不是深拷貝
Object中的clone()方法規定了拷貝的一般協議,可以參看API文檔
- 對於任何對象 x,表達式:x.clone() != x,克隆對象與原始對象不是同一個對象
- x.clone().getClass() == x.getClass() ,克隆對象與原始對象是同一種類型
- x.clone().equals(x) 為true
這三點並不是必須的,並不是必須的,並不是必須的,但是除非特殊情況,否則建議應該都滿足
Object 類本身不實現接口 Cloneable
所以,如果在類型為Object的對象上調用clone方法,會拋出異常
Java語言通過Object提供的protected方法以及Cloneable標記接口機制
定義了一套復制對象的工作流程:
- 實現Cloneable接口
- 覆蓋或者使用繼承而來的clone()方法
對於最簡單的原型模式 的應用,只需要原型類完成這步即可
這就是Java中原型模式型使用方式
因為在Java中,所有的對象直接或者間接地繼承Object
所以始終內置的隱含了Object這一抽象角色
我們的示例代碼中,Prototype 就是相當於java中的Object
圖中Prototype 為具體的原型類(提供了clone方法的類)
擁有管理器的原型模式
原型模式中最為關鍵的是調用某個對象的拷貝方法,進行原始對象的復制
所以原型模式一種常見的用法就是借助於這個"原始對象",達到工廠方法的效果
客戶端中保存一個ConcretePrototype類型的對象
后續的對象創建獲取就是客戶端通過這個內部的對象,調用它的拷貝方法進行進一步的操作
如果產品結構比較簡單,可能只需要幾種類型的對象即可
上面的原型結構比較適合,客戶端自己保存所有的對象
但是
如果產品等級結構比較雜亂,或者說要創建的原型對象是數目並不是固定的
又可以進一步將管理對象的職責進行提取分離,抽象出來一個管理器的角色
專門用於管理這些對象
這種帶管理器的原型模式中,客戶端就不在持有原型對象的引用了,也就是客戶端不在擁有原型對象
取而代之的是通過管理器獲取
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角色中也增加相關代碼
看得出來,從對象管理器中獲取的對象,都是原有對象的一個clone 並不是相同的對象
帶管理器的原型模式也叫做 登記形式的原型模式
登記就是相當於在管理器中備案
適用場景
為什么要使用原型模式?
簡簡單單的new一個對象不好么?為什么非要復制呢?
當創建新的對象的過程較為復雜時,使用原型模式可以簡化對象的創建過程
比如初始化占用時間較長,這種情況下創建一個對象將不再簡單,所以考慮原型模式
對於工廠模式中,工廠需要有與產品等級結構相同的結構
一個工廠生產一種產品
然而,如果產品的等級結構容易發生變化的話,工廠類的等級結構可能不得不進行變化
也就是說對於產品等級結構容易變化的場景來說,工廠模式將會不方便
如果采用
原型模式,那么就不再需要關注產品的等級結構,產品的等級結構可以隨意變動
因為原型模式僅僅關注具體的產品對象,對象之間的結構以及結構的變化並不會產生影響
所以在
這種情況下,原型模式擁有比工廠模式更為靈活,擴展性更好
不過
代價是每個類中都必須有一個clone方法,對象的創建邏輯從每個工廠轉移到了每個clone方法中
在框架中使用原型模式可以與生成的實例進行解耦
框架中面向抽象進行編程,只關注他的抽象類型,不關注他的具體類型
具體的對象可以通過配置文件等方式注入
框架借助於原型模式可以獲得這種類型的對象,而完全不用關注這個類型
否則當你使用某種類型時,你必然需要創建對象,也就是始終要接觸到具體的類型
這種方法就可以讓你永遠不知道具體的類型,徹底的分離
總結
原型模式的根本在於復制,所以依賴拷貝方法,java也內置了這種模式
在java中使用時,只需要實現Cloneable接口並且重寫或者使用繼承而來的clone方法即可
原型模式可以很好地隱藏創建對象的具體類型
當創建一個對象變得復雜時,我們可以考慮使用原型模式 通過復制的方式簡化對象的創建過程
但是這有一個前提,那就是復制對象相對比較簡單
但是,但是,但是,有的時候,復制一個對象本身卻也是非常復雜的,一般可以借助於序列化來進行
而且,每一個類都需要擁有clone方法,當需要對已有的類進行擴展改造時,clone方法也需要進行修改
這並不復合開閉原則








