標籤:style blog http java color get
引子
古人云:書非借不能讀也。
現在 IT 書籍更新快、價格貴、品質水平更是參差不齊,實在不忍心看到用自己的血汗錢買的書不到半年就要被淘汰
更不想供養使用金山快譯、詞霸等現代化工具的翻譯們。
於是去書店辦了張借書卡,這樣便沒有了後顧之憂了——書不好我可以換嘛!
但是,借書也有不爽的地方,就是看到有用或者比較重要的地方,不能在書旁標記下來。
一般會將這頁內容複印下來,這樣作為自己的東西就可以對其圈圈畫畫,儲存下來了。
在軟體設計中,往往也會遇到類似或者相似的問題,GOF 將這種解決方案叫作原型模式。
也許原形模式會給你一些新的啟迪。
定義與結構
原型模式屬於對象建立模式,GOF 給它的定義為:用原型執行個體指定建立對象的種類,並且通過拷貝這些原型建立新的對象。
在 Java 中提供了 clone()方法來實現對象的複製,所以 Prototype 模式實現變得簡單許多
使用複製方式來建立對象與同樣用來建立對象的原廠模式有什麼不同?
前面已經提過原廠模式對新產品的適應能力比較弱:建立新的產品時,就必須修改或者增加工廠角色。
而且為了建立產品對象要先額外的建立一個工廠對象。那通過原型模式來建立對象會是什麼樣子呢?
原型模式結構
- 客戶角色:讓一個原型複製自己來得到一個新對象。
- 抽象原型角色:實現了自己的 clone 方法,扮演這種角色的類通常是抽象類別,且它具有許多具體的子類。
- 具體原型角色:被複製的對象,為抽象原型角色的具體子類。
先上一張類圖
按照定義客戶角色不僅要負責使用對象,而且還要負責對象原型的產生和複製。
這樣造成客戶角色分工就不是很明確,所以我們把對象原型產生和複製功能單拿出來放到一個原型管理器中。
原型管理器維護了已有原型的清單。客戶在使用時會向原型管理器發出請求,而且可以修改原型管理器維護的清單。
這樣客戶不需要編碼就可以實現系統的擴充。
類圖表示
分析
對於抽象原型角色和具體原型角色,它們就是一個繼承或者實現關係,沒有什麼好玩的,記住實現好 clone 方法就好了。
那麼客戶是怎麼來使用這些角色的對象的呢?
最簡單的方式就是:
//先 new 一個具體原型角色作為樣本 Prototype p = new ConcretePrototype(); …… //使用原型 p 複製出一個新對象 p1 Prototype p1 = (Prototype)p.clone();
View Code
當然這隻是簡單的表述原型模式的運行過程。
實際運用中,客戶程式與原型角色之間往往存在一個原型管理器(例子見下)。
因此建立原型角色、拷貝原型角色就與客戶程式分離開來。
這時才能真正的體會到原型模式帶給我們的效果。
//使用原型管理器後,客戶獲得對象的方式Prototype p1 = PrototypeManager. getManager().getPrototype(“ConcretePrototype”);
View Code
上面提到的原型管理器的實現,簡單來說就是對原型清單的維護。
可以考慮一下幾點:
- 要儲存一個原型對象的清單,我們可以使用一個 HashMap 來實現,使原型對象和它的名字相對應;
- 原型管理器只需要一個就夠了,所以可以使用單例模式來實現控制;
- 實現得到、註冊、刪除原型對象的功能只是對 HashMap 的對應操作而已。
1 class PrototypeManager { 2 private static PrototypeManager pm; 3 private Map prototypes=null; 4 private PrototypeManager() { 5 prototypes=new HashMap(); 6 } 7 8 //使用單例模式來得到原型管理器的唯一執行個體 9 public static PrototypeManager getManager() {10 if(pm==null) {11 pm=new PrototypeManager();12 }13 return pm;14 }15 public void register(String name , Object prototype) {16 prototypes.put(name , prototype);17 }18 public void unregister(String name) {19 prototypes.remove(name);20 }21 public Prototype getPrototype(String name) {22 if(prototypes.containsKey(name)) {23 //將清單中對應原型的複製品返回給客戶24 return (Prototype) ((Prototype)prototypes.get(name)).clone();25 }else {26 Prototype object=null;27 try {28 object =(Prototype)Class.forName(name).newInstance();29 register(name , object);30 } catch(Exception e) {31 System.err.println("Class "+name+"沒有定義!");32 }33 return object;34 。。。View Code
這樣當客戶自訂新的產品對象時,同時向原型管理器註冊一個原型對象,而使用的類只需要根據客戶的需要來從原型管理器中得到一個對象就可以了。
這樣就使得功能擴充變得容易些。
原型模式與其它建立型模式有著相同的特點:它們都將具體產品的建立過程進行封裝,使得客戶對建立不可知。
就像上面例子中一樣,客戶程式僅僅知道一個抽象產品的介面。
當然它還有過人之處:
- 通過增加或者刪除原型管理器中註冊的對象,可以比其它建立型模式更方便的在運行時增加或者刪除產品。
- 如果一個對象的建立總是由幾種固定組件不同方式組合而成;
- 如果對象之間僅僅執行個體屬性不同。
- 將不同情況的對象緩衝起來,直接複製使用。
- 也許這比採用傳遞參數重新 new 一個對象要來的快一些。
你也許已經發現原型模式與原廠模式有著千絲萬縷的聯絡:
- 原型管理器不就是一個工廠麼。
- 當然這個工廠經過了改進(例如上例採用了 java 的反射機制),去掉了像抽象原廠模式或者Factory 方法模式那樣繁多的子類。
- 因此可以說原型模式就是在原廠模式的基礎上加入了複製方法。
也許你要說:我實在看不出來使用 clone 方法產生對象和 new 一個對象有什麼區別
- 原型模式使用 clone 能夠動態抽取當前對象運行時的狀態並且複製到新的對象中,新對象就可以在此基礎上進行操作而不損壞原有對象;
- new 只能得到一個剛初始化的對象,而在實際應用中,這往往是不夠的。
- 特別當你的系統需要良好的擴充性時,在設計中使用原型模式也是很必要的。
- 比如說,你的系統可以讓客戶自訂自己需要的類別,但是這種類別的初始化可能需要傳遞多於已有類別的參數,而這使得用它的類將不知道怎麼來初始化它(因為已經寫掛了),除非對類進行修改。
- 可見 clone 方法是不能使用建構函式來代替的。
分析了這麼多了,舉一個使用原型模式較為經典的例子:
績效考核軟體要對今年的各種考核心數據進行年度分析,而這一組資料是存放在資料庫中的。
一般我們會將這一組資料封裝在一個類中,然後將此類的一個執行個體作為參數傳入分析演算法中進行分析,得到的分析結果返回到類中相應的變數中。
假設我們決定對這組資料還要做另外一種分析以對分析結果進行比較評定。
這時對封裝有這組資料的類進行 clone 要比再次串連資料庫得到資料好的多。
任何模式都是存在缺陷的。
原型模式主要的缺陷就是每個原型必須含有 clone 方法,在已有類的基礎上來添加 clone 操作是比較困難的;
而且當內部包括一些不支援 copy 或者循環參考的對象時,實現就更加困難了。
總結
由於 clone 方法在 java 實現中有著一定的弊端和風險,所以 clone 方法是不建議使用的。
因此很少能在 java 應用中看到原型模式的使用。
但是原型模式還是能夠給我們一些啟迪。
@成鵬致遠
(blogs:lcw.cnblogs.com)
(email:[email protected])
(qq:552158509)