Java設計模式(五) 原型模式詳解
一、引言
在開發過程中,有時會遇到為一個類建立多個例項的情況,這些例項內部成員往往完全相同或有細微的差異,而且例項的建立開銷比較大或者需要輸入較多引數,如果能通過複製一個已建立的物件例項來重複建立多個相同的物件,這就可以大大減少建立物件的開銷,這個時候就需要原型模式。
二、模式詳解
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()方法具備深拷貝功能後,複製後的建立與原簡歷被獨立開來。