以前聽過這樣一句話:“程序員的最高境界就是Ctrl+C、Ctrl+V”,我們先不論這句話的對錯,就論這個過程,這個過程我們都知道無非就是復制一個對象,然后將其不斷地粘貼。這樣的過程我們可以將其稱之為“克隆”。再如我們應聘的時候打印了那么多的簡歷。
克隆我們都清楚,就是用一個物體復制若干個一模一樣物體。同樣,在面向對象系統中,我們同樣可以利用克隆技術來克隆出若干個一模一樣的對象。在應用程序中,有些對象比較復雜,其創建過程過於復雜,而且我們又需要頻繁的利用該對象,如果這個時候我們按照常規思維new該對象,那么務必會帶來非常多的麻煩,這個時候我們就希望可以利用一個已有的對象來不斷對他進行復制就好了,這就是編程中的“克隆”。這里原型模式就可以滿足我們的“克隆”,在原型模式中我們可以利用過一個原型對象來指明我們所要創建對象的類型,然后通過復制這個對象的方法來獲得與該對象一模一樣的對象實例。這就是原型模式的設計目的。
一、模式定義
通過前面的簡單介紹我們就可以基本確定原型模式的定義了。所謂原型模式就是用原型實例指定創建對象的種類,並且通過復制這些原型創建新的對象。
在原型模式中,所發動創建的對象通過請求原型對象來拷貝原型對象自己來實現創建過程,當然所發動創建的對象需要知道原型對象的類型。這里也就是說所發動創建的對象只需要知道原型對象的類型就可以獲得更多的原型實例對象,至於這些原型對象時如何創建的根本不需要關心。
講到原型模式了,我們就不得不區分兩個概念:深拷貝、淺拷貝。
淺拷貝:使用一個已知實例對新創建實例的成員變量逐個賦值,這個方式被稱為淺拷貝。
深拷貝:當一個類的拷貝構造方法,不僅要復制對象的所有非引用成員變量值,還要為引用類型的成員變量創建新的實例,並且初始化為形式參數實例值。
對於深拷貝和淺拷貝的詳細情況,請參考這里:漸析java的淺拷貝和深拷貝
二、模式結構
下圖是原型模式的UML結構圖:
原型模式主要包含如下三個角色:
Prototype:抽象原型類。聲明克隆自身的接口。
ConcretePrototype:具體原型類。實現克隆的具體操作。
Client:客戶類。讓一個原型克隆自身,從而獲得一個新的對象。
我們都知道Object是祖宗,所有的Java類都繼承至Object,而Object類提供了一個clone()方法,該方法可以將一個java對象復制一份,因此在java中可以直接使用clone()方法來復制一個對象。但是需要實現clone的Java類必須要實現一個接口:Cloneable.該接口表示該類能夠復制且具體復制的能力,如果不實現該接口而直接調用clone()方法會拋出CloneNotSupportedException異常。如下:
public class PrototypeDemo implements Cloneable{ public Object clone(){ Object object = null; try { object = super.clone(); } catch (CloneNotSupportedException exception) { System.err.println("Not support cloneable"); } return object; } …… }
Java中任何實現了Cloneable接口的類都可以通過調用clone()方法來復制一份自身然后傳給調用者。一般而言,clone()方法滿足:
(1) 對任何的對象x,都有x.clone() !=x,即克隆對象與原對象不是同一個對象。
(2) 對任何的對象x,都有x.clone().getClass()==x.getClass(),即克隆對象與原對象的類型一樣。
(3) 如果對象x的equals()方法定義恰當,那么x.clone().equals(x)應該成立。
三、模式實現
復印簡歷各位都應該做過吧!這里我們利用原型模式來模擬復印簡歷。
簡歷:Resume.java
public class Resume implements Cloneable { private String name; private String birthday; private String sex; private String school; private String timeArea; private String company; /** * 構造函數:初始化簡歷賦值姓名 */ public Resume(String name){ this.name = name; } /** * @desc 設定個人基本信息 * @param birthday 生日 * @param sex 性別 * @param school 畢業學校 * @return void */ public void setPersonInfo(String birthday,String sex,String school){ this.birthday = birthday; this.sex = sex; this.school = school; } /** * @desc 設定工作經歷 * @param timeArea 工作年限 * @param company 所在公司 * @return void */ public void setWorkExperience(String timeArea,String company){ this.timeArea = timeArea; this.company = company; } /** * 克隆該實例 */ public Object clone(){ Resume resume = null; try { resume = (Resume) super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return resume; } public void display(){ System.out.println("姓名:" + name); System.out.println("生日:" + birthday + ",性別:" + sex + ",畢業學校:" + school); System.out.println("工作年限:" + timeArea + ",公司:" + company); } }
客戶端:Client.java
public class Client { public static void main(String[] args) { //原型A對象 Resume a = new Resume("小李子"); a.setPersonInfo("2.16", "男", "XX大學"); a.setWorkExperience("2012.09.05", "XX科技有限公司"); //克隆B對象 Resume b = (Resume) a.clone(); //輸出A和B對象 System.out.println("----------------A--------------"); a.display(); System.out.println("----------------B--------------"); b.display(); /* * 測試A==B? * 對任何的對象x,都有x.clone() !=x,即克隆對象與原對象不是同一個對象 */ System.out.print("A==B?"); System.out.println( a == b); /* * 對任何的對象x,都有x.clone().getClass()==x.getClass(),即克隆對象與原對象的類型一樣。 */ System.out.print("A.getClass()==B.getClass()?"); System.out.println(a.getClass() == b.getClass()); } }
運行結果:
四、模式優缺點
優點
1、如果創建新的對象比較復雜時,可以利用原型模式簡化對象的創建過程,同時也能夠提高效率。
2、可以使用深克隆保持對象的狀態。
3、原型模式提供了簡化的創建結構。
缺點
1、在實現深克隆的時候可能需要比較復雜的代碼。
2、需要為每一個類配備一個克隆方法,而且這個克隆方法需要對類的功能進行通盤考慮,這對全新的類來說不是很難,但對已有的類進行改造時,不一定是件容易的事,必須修改其源代碼,違背了“開閉原則”。
五、模式使用場景
1、如果創建新對象成本較大,我們可以利用已有的對象進行復制來獲得。
2、如果系統要保存對象的狀態,而對象的狀態變化很小,或者對象本身占內存不大的時候,也可以使用原型模式配合備忘錄模式來應用。相反,如果對象的狀態變化很大,或者對象占用的內存很大,那么采用狀態模式會比原型模式更好。
3、需要避免使用分層次的工廠類來創建分層次的對象,並且類的實例對象只有一個或很少的幾個組合狀態,通過復制原型對象得到新實例可能比使用構造函數創建一個新實例更加方便。
六、模式總結
1、原型模式向客戶隱藏了創建對象的復雜性。客戶只需要知道要創建對象的類型,然后通過請求就可以獲得和該對象一模一樣的新對象,無須知道具體的創建過程。
2、克隆分為淺克隆和深克隆兩種。
3、我們雖然可以利用原型模式來獲得一個新對象,但有時對象的復制可能會相當的復雜,比如深克隆。