7.原型模式-Prototype
初識原型模式
定義
? 用原型實例指定創建對象的種類,並通過拷貝這些原型創建新的對象。
結構和說明
Prototype:聲明一個克隆自身的接口,用來約束想要克隆自己的類,要求它們都要實現這裏定義的克隆方法。
ConcretePrototype:實現Prototype接口的類,這些類真正實現克隆自身的功能
Client:使用原型的客戶端,首先要獲取到原型實例對象,然後通過原型實例克隆自身來創建新的對象實例。
Prototype類需要具備以下兩個條件:
? 實現Cloneable接口。它的作用只有一個,就是在運行時通知虛擬機可以安全地在實現了此接口的類上使用clone方法。在java虛擬機中,只有實現了這個接口的類才可以被拷貝,否則在運行時會拋出CloneNotSupportedException異常。?
重寫Object類中的clone方法。Java中,所有類的父類都是Object類,Object類中有一個clone方法,作用是返回對象的一個拷貝,但是其作用域protected類型的,一般的類無法調用,因此,Prototype類需要將clone方法的作用域修改為public類型。
? 原型模式是一種比較簡單的模式,也非常容易理解,實現一個接口,重寫一個方法即完成了原型模式。在實際應用中,原型模式很少單獨出現。經常與其他模式混用,他的原型類Prototype也常用抽象類來替代。
體會原型模式
訂單處理系統? 考慮這樣一個實際應用:訂單處理系統。? 現在有一個訂單處理的系統,裏面有個保存訂單的業務功能,在這個業務功能裏面,客戶有這麽一個需求:每當訂單的預定產品數量超過1000的時候,就需要把訂單拆成兩份訂單來保存,如果拆成兩份訂單後,還是超過1000,那就繼續拆分,直到每份訂單的預定產品數量不超過1000。至於為什麽要拆分,原因是好進行訂單的後續處理,後續是由人工來處理,每個人工工作小組的處理能力上限是1000。
? 根據業務,目前的訂單類型被分成兩種:一種是個人訂單,一種是公司訂單。現在想要實現一個通用的訂單處理系統,也就是說,不管具體是什麽類型的訂單,都要能夠正常的處理。該怎麽實現呢?
? 不用模式的解決方案--看代碼
? 存在的問題
? 仔細想想,真的沒有關心訂單的類型和具體實現嗎?答案是“否定的”。事實上在實現訂單處理的時候,上面的實現是按照訂單的類型和具體實現來處理的,就是instanceof的那一段。有朋友可能會問,這樣實現有何不可嗎?這樣的實現有如下幾個問題
(1)既然想要實現通用的訂單處理,那麽對於訂單處理的實現對象,是不應該知道訂單的具體實現的,更不應該依賴訂單的具體實現。但是上面的實現中,很明顯訂單處理的對象依賴了訂單的具體實現對象。
(2)這種實現方式另外一個問題就是:難以擴展新的訂單類型。假如現在要加入一個大客戶專用訂單的類型,那麽就需要修改訂單處理的對象,要在裏面添加對新的訂單類型的支持,這算哪門子的通用處理。? 因此,上面的實現是不太好的,把上面的問題再抽象描述一下:已經有了某個對象實例後,如何能夠快速簡單地創建出更多的這種對象?
使用模式的解決方案
理解原型模式
認識原型模式
1:原型模式的功能原型模式的功能實際上包含兩個方面:
(1)一個是通過克隆來創建新的對象實例
(2)另一個是為克隆出來的新的對象實例復制原型實例屬性的值 原型模式要實現的主要功能就是:通過克隆來創建新的對象實例。一般來講,新創建出來的實例的數據是和原型實例一樣的。但是具體如何實現克隆,需要由程序自行實現,原型模式並沒有統一的要求和實現算法。
2:原型與new? 原型模式從某種意義上說,就像是new操作,在前面的例子實現中,克隆方法就是使用new來實現的,但請註意,只是“類似於new”而不是“就是new”。克隆方法和new操作最明顯的不同就在於:new一個對象實例,一般屬性是沒有值的,或者是只有默認值;如果是克隆得到的一個實例,通常屬性是有值的,屬性的值就是原型對象實例在克隆的時候,原型對象實例的屬性的值。
3:原型實例和克隆的實例? 原型實例和克隆出來的實例,本質上是不同的實例,克隆完成後,它們之間是沒有關聯的,如果克隆完成後,克隆出來的實例的屬性的值發生了改變,是不會影響到原型實例的。
4:原型模式的調用順序示意圖
Java中的克隆方法 在Java語言中已經提供了clone方法,定義在Object類中。需要克隆功能的類,只需要實現java.lang.Cloneable接口,這個接口沒有需要實現的方法,是一個標識接口。
淺度克隆和深度克隆
什麽是淺度克隆?什麽是深度克隆呢?簡單地解釋一下:
(1)淺度克隆:只負責克隆按值傳遞的數據(比如:基本數據類型、String類型)
(2)深度克隆:除了淺度克隆要克隆的值外,還負責克隆引用類型的數據,基本上就是被克隆實例所有的屬性的數據都會被克隆出來.
? 深度克隆還有一個特點,如果被克隆的對象裏面的屬性數據是引用類型,也就是屬性的類型也是對象,那麽需要一直遞歸的克隆下去。這也意味著,要想深度克隆成功,必須要整個克隆所涉及的對象都要正確實現克隆方法,如果其中有一個沒有正確實現克隆,那麽就會導致克隆失敗。
原型管理器
? 如果一個系統中原型的數目不固定,比如系統中的原型可以被動態的創建和銷毀,那麽就需要在系統中維護一個當前可用的原型的註冊表,這個註冊表就被稱為原型管理器。 其實如果把原型當成一個資源的話,原型管理器就相當於一個資源管理器,在系統開始運行的時候初始化,然後運行期間可以動態的添加資源和銷毀資源。從這個角度看,原型管理器就可以相當於一個緩存資源的實現,只不過裏面緩存和管理的是原型實例而已。
? 有了原型管理器過後,一般情況下,除了向原型管理器裏面添加原型對象的時候是通過new來創造的對象,其余時候都是通過向原型管理器來請求原型實例,然後通過克隆方法來獲取新的對象實例,這就可以實現動態管理、或者動態切換具體的實現對象實例。
原型模式的優缺點
1:對客戶端隱藏具體的實現類型
2:在運行時動態改變具體的實現類型
3:深度克隆方法實現會比較困難
?
思考原型模式
原型模式的本質
原型模式的本質是:克隆生成對象
何時選用原型模式
1:如果一個系統想要獨立於它想要使用的對象時,可以使用原型模式,讓系統只面向接口編程,在系統需要新的對象的時候,可以通過克隆原型來得到
2:如果需要實例化的類是在運行時刻動態指定時,可以使用原型模式,通過克隆原型來得到需要的實例
? 使用原型模式創建對象比直接new一個對象在性能上要好的多,因為Object類的clone方法是一個本地方法,
(protected native Object clone())它直接操作內存中的二進制流,特別是復制大對象時,性能的差別非常明顯。
? 使用原型模式的另一個好處是簡化對象的創建,使得創建對象就像我們在編輯文檔時的復制粘貼一樣簡單。
因為以上優點,所以在需要重復地創建相似對象時可以考慮使用原型模式。比如需要在一個循環體內創建對象,假如對象創建過程比較復雜或者循環次數很多的話,使用原型模式不但可以簡化創建過程,而且可以使系統的整體性能提高很多。
原型模式的註意事項
? 使用原型模式復制對象不會調用類的構造方法。因為對象的復制是通過調用Object類的clone方法來完成的,它直接在內存中復制數據,因此不會調用到類的構造方法。不但構造方法中的代碼不會執行,甚至連訪問權限都對原型模式無效。還記得單例模式嗎?單例模式中,只要將構造方法的訪問權限設置為private型,就可以實現單例。但是clone方法直接無視構造方法的權限,所以,單例模式與原型模式是沖突的,在使用時要特別註意。
? 深拷貝與淺拷貝。Object類的clone方法只會拷貝對象中的基本的數據類型,對於數組、容器對象、引用對象等都不會拷貝,這就是淺拷貝。如果要實現深拷貝,必須將原型模式中的數組、容器對象、引用對象等另行拷貝。
由於ArrayList不是基本類型,所以成員變量list,不會被拷貝,需要我們自己實現深拷貝,幸運的是java提供的大部分的容器類都實現了Cloneable接口。所以實現深拷貝並不是特別困難。
? PS:深拷貝與淺拷貝問題中,會發生深拷貝的有java中的8中基本類型以及他們的封裝類型,另外還有String類型。其余的都是淺拷貝。
代碼地址: https://gitee.com/weixiaotao1992/DesignPatternsForJava
7.原型模式-Prototype