1. 程式人生 > >Java設計模式(五) 原型模式詳解

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()方法具備深拷貝功能後,複製後的建立與原簡歷被獨立開來。

三、總結

       原型模式可以說是所有設計模式中最簡單的一個,它沒有複雜的繼承體系,只需要使需要具有拷貝功能的類實現Cloneable介面並重寫clone()方法即可。但它的應用卻及其廣泛,它將對一個物件中各個欄位(不管是私有的還是共有的)的複製操作封裝在了clone()方法中,這樣,使用該類的使用者就不需要對物件中的各個欄位的細節進行了解,直接呼叫clone()方法就可以實現物件的拷貝,而且,通過clone()方法還可以為不同的欄位設定被複制的許可權,從而允許僅對可以被複制的欄位進行復制。