一、引言
在開發過程中,有時會遇到為一個類建立多個執行個體的情況,這些執行個體內部成員往往完全相同或有細微的差異,而且執行個體的建立開銷比較大或者需要輸入較多參數,如果能通過複製一個已建立的對象執行個體來重複建立多個相同的對象,這就可以大大減少建立對象的開銷,這個時候就需要原型模式。
二、模式詳解
1、模式分析
原型模式可以通過一個對象執行個體確定建立對象的種類,並且通過拷貝建立新的執行個體。總得來說,原型模式實際上就是從一個對象建立另一個新的對象,使新的對象有具有原對象的特徵。
圖1給出了原型模式的UML圖,可以看出原型模式的結構非常簡單。首先,所有可以作為原型的類中都應該有一個用於複製自身的方法clone(),因此,我們可以抽象出一個抽象原型類或介面,該類中只有一個clone(),所有的具體原型類都要實現該方法並定義複製自身的具體行為。在用戶端中通過調用具體原型對象的clone方法可以複製原型對象到一個新的對象中。
圖1 原型模式UML圖 2、具體實現
原型模式是一種應用及其廣泛的設計模式,Clone也是一種十分常見的操作,以至於在Java中,終極父類Object將Clone方法作為了所有類應具有的準系統,並且Java也提供了Cloneable介面(關於Cloneable介面的細節,請看我另一篇博文:JavaSE學習隨筆(一) Cloneable介面源碼分析與技術細節),這都方便了原型模式的實現。
我們還是以一個例子來說明原型模式的具體實現和作用。考慮一個寫簡曆的情境,簡曆中包括的資訊有姓名、性別、年齡、家庭成員和工作經驗幾點內容。現在的需求為由於需要向多個公司投遞建立簡曆,因此子建立了一份建立對象之後,還要求能夠對已建立的簡曆進行複製。此時,恰好有幾個另外有幾個同學也想找工作,為了方便,就把已建立的作為模版,然後根據自身的情況作了一些修改。
為了實現上述要求,我們首先定義一個工作經驗類,裡面有兩個成員變數分別為工作時間和公司名稱。
class WorkExperience { public String timeArea = null; public String company = null; }/*WorkExperience*/
接著定義一個簡曆類,該類相當於UML圖中的實體原型類,至於抽像原型類或介面,Java中已經我i我們提供了Cloneable介面,因此我們只需要實現它就可以了(實現的方法可以通過上文給出的連結到我另一篇博文中查看,在這裡我直接給出了介面的實現),我們首先來看一種實現方式。
class Resume implements Cloneable { public String name = null; public Integer age = null; public String sex = null; public ArrayList<String> famMem = new ArrayList<>(); public WorkExperience work = null; public Resume(String name) { this.name = name; work = new WorkExperience(); }// Resume public void setName(String name) { this.name = name; }// setName public void setPersonal(String sex, int age, ArrayList<String> famMem) { this.age = age; this.sex = sex; this.famMem = famMem; }// setPersonal public void setWork(String timeArea, String company) { work.timeArea = timeArea; work.company = company; }// setWork /** * 重些clone()方法為public類型,並調用Object類的本地clone()方法。 */ @Override public Resume clone() throws CloneNotSupportedException { return (Resume)super.clone(); }// clone public void display() { System.out.println(this.name + " " + this.sex + " " + this.age); System.out.print("Family member: "); for(String elem : famMem) System.out.print(elem + " "); System.out.println(); System.out.print("Work experience: " + this.work.timeArea); System.out.println(" " + this.work.company); }// display }/*Resume*/
接下來是用戶端代碼:
public class PrototypeDemo { public static void main(String[] args) throws CloneNotSupportedException { ArrayList<String> famMem = new ArrayList<>(); // 家庭成員名單 famMem.add("Papa"); famMem.add("Mama"); // 建立初始簡曆 Resume resume1 = new Resume("Jobs", famMem); resume1.setPersonal("Male", 26); resume1.setWork("2013/8/1 - 2015/6/30", "Huawei"); // 通過簡曆1複製出簡曆2,並對家庭成員和工作經驗進行修改 Resume resume2 = resume1.clone(); resume2.setName("Tom"); resume2.famMem.add("Brother"); resume2.setWork("2015/7/1 - 2016/6/30", "Baidu"); resume1.display(); resume2.display(); }// main }/*Pritotype*/
運行結果:
從運行結果上看,雖然Tom成功複製了Jobs的簡曆,但是隨後對Tom家庭成員和工作經驗的修改卻導致了Jobs的簡曆被同時修改,這是由於我們在實現clone() 方法時直接調用了Object類的本地clone()方法造成的,因為Object的clone()方法執行的是淺拷貝,因而Jobs和Tom的簡曆中的famMem和work欄位都指向了同一個對象執行個體。要想實現深拷貝,就必須要修改clone()方法(詳見:JavaSE學習隨筆(一) Cloneable介面源碼分析與技術細節):
class Resume implements Cloneable { public String name = null; public int age = 0; public String sex = null; public ArrayList<String> famMem = new ArrayList<>(); public WorkExperience work = null; public Resume(String name, ArrayList<String> famMem) { this.name = name; this.famMem = famMem; work = new WorkExperience(); }// Resume public void setName(String name) { this.name = name; }// setName public void setPersonal(String sex, int age) { this.age = age; this.sex = sex; }// setPersonal public void setWork(String timeArea, String company) { work.timeArea = timeArea; work.company = company; }// setWork /** * 重些clone()方法為public類型,為每個欄位都建立新的對象,已實現深拷貝功能。 */ @Override public Resume clone() throws CloneNotSupportedException { int age = this.age; String sex = this.sex; String name = new String(this.name); ArrayList<String> famMem = new ArrayList<>(this.famMem); Resume copy = new Resume(name, famMem); copy.setPersonal(sex, age); copy.setWork(this.work.timeArea, this.work.company); return copy; }// clone public void display() { System.out.println(this.name + " " + this.sex + " " + this.age); System.out.print("Family member: "); for(String elem : famMem) System.out.print(elem + " "); System.out.println(); System.out.print("Work experience: " + this.work.timeArea); System.out.println(" " + this.work.company); }// display }/*Resume*/
運行結果:
可以看出,使clone()方法具備深拷貝功能後,複製後的建立與原簡曆被獨立開來。
三、總結
原型模式可以說是所有設計模式中最簡單的一個,它沒有複雜的繼承體系,只需要使需要具有拷貝功能的類實現Cloneable介面並重寫clone()方法即可。但它的應用卻及其廣泛,它將對一個對象中各個欄位(不管是私人的還是共有的)的複製操作封裝在了clone()方法中,這樣,使用該類的使用者就不需要對對象中的各個欄位的細節進行瞭解,直接調用clone()方法就可以實現對象的拷貝,而且,通過clone()方法還可以為不同的欄位設定被複製的許可權,從而允許僅對可以被複製的欄位進行複製。