1. 程式人生 > >建立型設計模式之原型模式

建立型設計模式之原型模式

原型模式的介紹

原型二字代表該模式應該有一個樣板例項,使用者從這個樣板中複製出一個內部屬性一致的物件,這個過程也就是我們俗稱的“克隆”。被複制的例項就是我們所稱的“原型”。原型模式多用於建立複雜的或者構造耗時的例項,因為這種情況下,複製一個已經存在的例項可以使程式執行更高效。

原型模式定義

用原型例項指定建立物件的種類,並通過拷貝這些原型建立新的物件

原型模式使用場景

(1)類初始化需要消耗非常多的資源,通過原型拷貝避免這些消耗

(2)new 一個物件需要非常繁瑣的資料準備或訪問許可權。

(3)一個物件需要提供給其他物件訪問,而且各個呼叫者可能需要修改其值,可以考慮使用原型模式拷貝多個物件供呼叫者使用,即保護性拷貝

注意:通過實現Cloneable介面的原型模式在呼叫clone函式構造例項時並不一定比new操作快,只有當new物件較為耗時或成本較高時,通過clone方法才能有效率上的提高。因此在使用原型模式是要考慮構建物件的成本和做一些效率上的測試。 

原型模式的類圖

角色介紹:

  • Client - 客戶端
  • Prototype - 抽象類或介面,宣告具備clone能力
  • ConcretePrototype - 具體的原型類

原型模式的簡單實現

假設現在有一份文件,文件中有文字和圖片,某位使用者想要在文件上做一些內容修改,但是修改後的文件是否被採用是不確定的,所以使用者需要將當前文件拷貝一份,然後在文件副本上進行修改。這個原始文件就是我們上述所說的樣本例項,也就是將要被克隆的物件,我們稱之為原型。

文件型別,扮演的是ConcretePrototype角色,Cloneable代表的是Prototype角色

public class WorkDocument implements Cloneable {

    private String mText;//文字
    private ArrayList<String> mImages = new ArrayList<>();//圖片列表

    public WorkDocument(){
        System.out.println("WorkDocument的建構函式");
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        WorkDocument document = (WorkDocument) super.clone();
        this.mText = document.mText;
        this.mImages = document.mImages;
        return document;
    }

    public void showDocument(){
        System.out.println("============start===========");
        System.out.println("Text: " + mText);
        System.out.println("Image List:");
        for(String s : mImages){
            System.out.println("Image name: " + s);
        }
        System.out.println("=============end============");
    }

    public String getmText() {
        return mText;
    }

    public void setmText(String mText) {
        this.mText = mText;
    }

    public ArrayList<String> getmImages() {
        return mImages;
    }

    public void setmImage(String mImages) {
        this.mImages.add(mImages);
    }
}

下面看Client端

public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {

        //1、建立文件物件
        WorkDocument originDoc = new WorkDocument();
        //2、編輯文件
        originDoc.setmText("這是一篇文件");
        originDoc.setmImage("圖片1");
        originDoc.setmImage("圖片2");
        originDoc.setmImage("圖片3");
        originDoc.showDocument();
        //以原型文件為原型,拷貝一份副本
        WorkDocument doc2 = (WorkDocument) originDoc.clone();
        System.out.println("拷貝後:");
        doc2.showDocument();
        doc2.setmText("這是修改後的文字");
        System.out.println("修改後的副文字:");
        doc2.showDocument();
        System.out.println("修改後的原文字:");
        originDoc.showDocument();
    }

}

輸出結果

WorkDocument的建構函式
============start===========
Text: 這是一篇文件
Image List:
Image name: 圖片1
Image name: 圖片2
Image name: 圖片3
=============end============
拷貝後:
============start===========
Text: 這是一篇文件
Image List:
Image name: 圖片1
Image name: 圖片2
Image name: 圖片3
=============end============
修改後的副文字:
============start===========
Text: 這是修改後的文字
Image List:
Image name: 圖片1
Image name: 圖片2
Image name: 圖片3
=============end============
修改後的原文字:
============start===========
Text: 這是一篇文件
Image List:
Image name: 圖片1
Image name: 圖片2
Image name: 圖片3
=============end============

從上面可以看出,doc2是originDoc的一份拷貝,它們的內容一樣,doc2修改文字後並不會影響originDoc的文字。而且通過clone拷貝物件時並不會執行建構函式,如果需要在建構函式中做某些特殊的初始化操作時,在使用clone時要注意建構函式不會執行。

淺拷貝和深拷貝

上面的原型模式只是一個淺拷貝,這份拷貝並不是將原始的文件的所有欄位都重新構造了一份,而是副文字的欄位引用原始文字的欄位

也就是說當我們修改副文字時,原文字也會被修改,修改一下main函式:

public class Client {

    public static void main(String[] args) throws CloneNotSupportedException {

        //1、建立文件物件
        WorkDocument originDoc = new WorkDocument();
        //2、編輯文件
        originDoc.setmText("這是一篇文件");
        originDoc.setmImage("圖片1");
        originDoc.setmImage("圖片2");
        originDoc.setmImage("圖片3");
        originDoc.showDocument();
        //以原型文件為原型,拷貝一份副本
        WorkDocument doc2 = (WorkDocument) originDoc.clone();
        System.out.println("拷貝後:");
        doc2.showDocument();
        doc2.setmText("這是修改後的文字");
        doc2.setmImage("你好.png");//
        System.out.println("修改後的副文字:");
        doc2.showDocument();
        System.out.println("修改後的原文字:");
        originDoc.showDocument();
    }

}

輸出

WorkDocument的建構函式
============start===========
Text: 這是一篇文件
Image List:
Image name: 圖片1
Image name: 圖片2
Image name: 圖片3
=============end============
拷貝後:
============start===========
Text: 這是一篇文件
Image List:
Image name: 圖片1
Image name: 圖片2
Image name: 圖片3
=============end============
修改後的副文字:
============start===========
Text: 這是修改後的文字
Image List:
Image name: 圖片1
Image name: 圖片2
Image name: 圖片3
Image name: 你好.png
=============end============
修改後的原文字:
============start===========
Text: 這是一篇文件
Image List:
Image name: 圖片1
Image name: 圖片2
Image name: 圖片3
Image name: 你好.png
=============end============

我在doc2添加了一張“你好.png” 圖片,輸出結果是原文件和副文件都改變了,為什麼呢?因為上文中doc2中的this.mImages只是單純的指向originDoc中的this.mImages,並沒有重新構造一個mImages物件,所以修改doc2等於修改originDoc,那麼如何解決呢?答案就是採用深拷貝,即在拷貝物件時,對引用型欄位也要進行拷貝形式,clone修改如下:

@Override
    protected Object clone() throws CloneNotSupportedException {
        WorkDocument document = (WorkDocument) super.clone();
        this.mText = document.mText;
        //對mImages物件也要進行呼叫clone函式,深拷貝
        this.mImages = (ArrayList<String>) document.mImages.clone();
        return document;
    }

輸出

WorkDocument的建構函式
============start===========
Text: 這是一篇文件
Image List:
Image name: 圖片1
Image name: 圖片2
Image name: 圖片3
=============end============
拷貝後:
============start===========
Text: 這是一篇文件
Image List:
Image name: 圖片1
Image name: 圖片2
Image name: 圖片3
=============end============
修改後的副文字:
============start===========
Text: 這是修改後的文字
Image List:
Image name: 圖片1
Image name: 圖片2
Image name: 圖片3
Image name: 你好.png
=============end============
修改後的原文字:
============start===========
Text: 這是一篇文件
Image List:
Image name: 圖片1
Image name: 圖片2
Image name: 圖片3
=============end============

總結

原型模式是一個很簡單的模式,就是對原始物件進行拷貝,在開發過程中為了減少錯誤,建議大家使用深拷貝,避免操作副本時影響原始物件。使用原型模式可以解決構建複雜物件的資源消耗問題,還有一個重要用途是保護原物件,原物件對外只是可讀的。

優點:

(1)原型模式是在記憶體中二進位制流的拷貝,要比直接new一個物件效能好,特別要在一個迴圈體內產生大量物件時,使用原型模式優點更明顯

缺點:

(2)建構函式不會執行