23種設計模式(八)-原型設計模式

盛開的太陽發表於2021-07-11

一. 什麼是原型設計模式?

1.1 原型設計模式的概念

​ 原型設計模式的思想類似於我們常用的複製貼上功能. 把一個地方的檔案複製到另外一個地方, 複製完以後, 兩個檔案的內容是一模一樣的. 原型設計模式的精髓也在於此. 原型模式用於建立重複的物件,首先建立一個例項物件, 然後通過拷貝例項物件建立一個新的物件。這種模式類似於建立型模式。

使用原型模式建立物件非常高效,無須知道物件建立的細節.多用於建立複雜的或者構造耗時的例項,因為在這種情況下,複製一個已經存在的例項會更高效。

1.2 為什麼要使用原型設計模式?

  1. 通常, 類初始化的過程需要消耗很多的資源,這個資源包括資料、空間、時間資源等,通過原型拷貝降低這樣的消耗

  2. 通過new 去建立一個物件,需要非常繁瑣的步驟,如:資料準備和檢查訪問許可權等。使用原型模式可以簡化這些操作。

  3. 當一個物件需要被其他物件訪問或者操作時, 如果各個呼叫者都修改資料的可能性,那麼這時可以考慮原型設計模式拷貝多個物件以供呼叫者使用,即保護性複製。

原型設計模式通常使用在new一個資源很耗時的情況,相比new,使用原型模式的效率明顯提高。

二. 原型設計模式實現步驟

2.1 原型設計模式的結構

下面來看原型模式的UML圖:

23種設計模式(八)-原型設計模式

從上圖中可以看出原型設計模式的結構構成:

  1. 抽象原型類: 定義了具體原型物件鼻血實現的介面. 這裡是Cloneable介面
  2. 具體原型類: 實現了抽象原型類, 並重寫了clone方法.這個類的物件就是可被複制的物件.
  3. 訪問類: 實現使用具體原型類克隆出新類的類

2.2 原型模式實現的步驟

原型模式主要用於物件的複製, 他的核心是Propototype原型類, 下面來看看在java中, 實現原型模式的步驟:

第一步: 原型類Prototype實現Cloneable介面,
第二步: 重寫Object的clone()方法.
第三步: 在目標類也就是PrototypeTest型別呼叫Prototype類的clone方法, 實現物件的複製.

Cloneable介面: 在Java語言中有一個自帶的Cloneable介面, 這個介面的作用只有一個, 就是在程式執行的時候通知虛擬機器可以安全的在實現了此介面的類上使用clone()方法. 在java虛擬機器中, 只有實現了這個介面的類才能被拷貝, 否則會丟擲異常CloneNotSupportedException.

public interface Cloneable {
}

重寫Object的clone()方法: 在java中所有的類都有一個父類Object , Object裡面定義了一個clone()方法, 但是這個clone()方法是protected型別的, 在其他地方不能隨便使用, 所以, 我們需要重寫clone方法, 並將其作用域設定為public.

public class Object {
    protected native Object clone() throws CloneNotSupportedException;
}

原型模式很少單獨出現。經常與其他模式混用,他的原型類Prototype也常用抽象類來替代。

三. 原型設計模式的案例

我們就以上面的UML為例, 來感受一下原型設計模式拷貝出的物件

  1. Prototype原型類, 實現了Cloneable介面, 並重寫了Object的clone()方法
public class Prototype implements Cloneable{

    private String name;

    private int age;

    private String sex;

    public Prototype(String name, int age, String sex) {
        this.name = name;
        this.age = age;
        this. sex = sex;
    }


    /**
     * 重寫object的clone()方法, 並將其作用域設定為public
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    @Override
    public String toString() {
        return "Prototype{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}
  1. PrototypeTest類, 通過呼叫Prototype的clone()方法來克隆已經建立的Prototype物件.
public class PrototypeTest {

    public static void main(String[] args) throws CloneNotSupportedException {
        Prototype prototype = new Prototype("張三", 8, "男");
        Prototype cloneObject = (Prototype)prototype.clone();

        System.out.println(cloneObject);
    }
}

執行結果

Prototype{name='張三', age=8, sex='男'}

四. 原型設計模式實現的型別.

原型設計模式實現的型別有兩種: 淺拷貝深拷貝

4.1 淺拷貝

淺拷貝指的是在建立一個物件的時候, 新物件的屬性和原來物件的屬性完全相同, 對於非基本型別屬性, 扔指向原有屬性所指向的物件的記憶體地址。

還是用上面的demo來說明:

public class Prototype implements Cloneable{

    private String name;

    private int age;

    private String sex;

    private ArrayList<String> hobbies;

    public Prototype(String name, int age, String sex, ArrayList<String> hobbies) {
        this.name = name;
        this.age = age;
        this. sex = sex;
        this.hobbies = hobbies;
    }



    /**
     * 重寫object的clone()方法, 並將其作用域設定為public
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    public Prototype clone() throws CloneNotSupportedException {
        Prototype clone = (Prototype)super.clone();
        
        return clone;
    }
}

public class PrototypeTest {

    public static void main(String[] args) throws CloneNotSupportedException {
        ArrayList hobbies = new ArrayList();
        hobbies.add("籃球");
        hobbies.add("排期");
        Prototype prototype = new Prototype("張三", 8, "男", hobbies);
        Prototype cloneObject = (Prototype)prototype.clone();

        System.out.println("比較克隆前後的物件:"+(prototype == cloneObject));
        System.out.println("比較克隆前後的List<String>屬性:" + (prototype.getHobbies() == cloneObject.getHobbies()));

    }
}

執行結果:

比較克隆前後的物件:false
比較克隆前後的List屬性:true

我們在比較hobbies的時候, 使用的是 的含義是地址和值都一樣才返回true。

第一個返回結果是false。說明克隆後重新建立了一個物件。

第二個結果返回的是ture,說明克隆後引用型別的物件指向了原來物件的地址。

這是一種淺拷貝, 預設的拷貝方式是淺拷貝

4.2 深拷貝

深拷貝是指在建立一個物件的時候, 屬性中引用的其他物件也會被克隆,不再指向原有的地址。

我們還是使用上面的案例, 將上面的淺拷貝變成深拷貝

public class Prototype implements Cloneable{

    private String name;
    private int age;
    private String sex;
    private ArrayList<String> hobbies;

    public Prototype(String name, int age, String sex, ArrayList<String> hobbies) {
        this.name = name;
        this.age = age;
        this. sex = sex;
        this.hobbies = hobbies;
    }

    /**
     * 重寫object的clone()方法, 並將其作用域設定為public
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    public Prototype clone() throws CloneNotSupportedException {
        Prototype clone = (Prototype)super.clone();
        System.out.println("淺拷貝:" + (clone.hobbies == this.hobbies));
        clone.hobbies = (ArrayList<String>) (this.hobbies).clone();
        System.out.println("深拷貝:" + (clone.hobbies == this.hobbies));
        return clone;
    }

    @Override
    public String toString() {
        return "Prototype{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }
}


public class PrototypeTest {

    public static void main(String[] args) throws CloneNotSupportedException {
        ArrayList hobbies = new ArrayList();
        hobbies.add("籃球");
        hobbies.add("排期");
        Prototype prototype = new Prototype("張三", 8, "男", hobbies);
        Prototype cloneObject = (Prototype)prototype.clone();

        System.out.println("比較克隆前後的物件:"+(prototype == cloneObject));
        System.out.println("比較克隆前後的List<String>屬性:" + (prototype.getHobbies() == cloneObject.getHobbies()));
    }
}

執行結果:

淺拷貝:true
深拷貝:false
比較克隆前後的物件:false
比較克隆前後的List屬性:false

這是一種深拷貝, 實現方式是

public Prototype clone() throws CloneNotSupportedException {
        Prototype clone = (Prototype)super.clone();
        System.out.println("淺拷貝:" + (clone.hobbies == this.hobbies));
        clone.hobbies = (ArrayList<String>) (this.hobbies).clone();
        System.out.println("深拷貝:" + (clone.hobbies == this.hobbies));
        return clone;
    }

對ArrayList使用了clone。這樣就保障物件重新建立。

原型模式是比較簡單的一個模式,核心就是對原始物件進行拷貝,為了減少錯誤,建議每次都進行深拷貝

五. 原型設計模式的優缺點

5.1 優點

在記憶體中二進位制流進行拷貝,要比直接new 一個物件效能要好。

  1. 如果建立新的物件比較複雜時,可以利用原型模式簡化物件的建立過程,同時也能提高效率
  2. 可以使用深拷貝保持原始物件的狀態
  3. 原型模式提供了簡化的建立結構

5.2 缺點

直接在記憶體中進行拷貝,是不會執行建構函式的,減少了約束。優點是減少了約束,缺點也是減少了約束。

  1. 在實現深拷貝的時候可能需要比較複雜的程式碼
  2. 需要為每一個類配備一個clone 方法,而且這個clone 方法需要對類的整體功能進行考慮,這對全新的類來說並不困難,但對已有的類進行改造時,並不是一件容易的事,必須修改其原始碼,違背了“開閉原則”

六. 原型模式的注意事項

使用原型模式複製物件不會呼叫類的構造方法。因為物件的複製是通過呼叫Object類的clone方法來完成的,它直接在記憶體中複製資料,因此不會呼叫到類的構造方法。

不但構造方法中的程式碼不會執行,甚至連訪問許可權都對原型模式無效。還記得單例模式嗎?單例模式中,只要將構造方法的訪問許可權設定為private型,就可以實現單例。但是clone方法直接無視構造方法的許可權,所以,單例模式與原型模式是衝突的,在使用時要特別注意。

深拷貝與淺拷貝。Object類的clone方法只會拷貝物件中的基本的資料型別,對於陣列、容器物件、引用物件等都不會拷貝,這就是淺拷貝。如果要實現深拷貝,必須將原型模式中的陣列、容器物件、引用物件等另行拷貝。例如

public class Prototype implements Cloneable{

    private String name;
    private int age;
    private String sex;
    private ArrayList<String> hobbies;

    /**
     * 重寫object的clone()方法, 並將其作用域設定為public
     * @return
     * @throws CloneNotSupportedException
     */
    @Override
    public Prototype clone() throws CloneNotSupportedException {
        Prototype clone = (Prototype)super.clone();
        clone.hobbies = (ArrayList<String>) (this.hobbies).clone();
        return clone;
    }
}

由於ArrayList不是基本型別,所以成員變數list,不會被拷貝,需要我們自己實現深拷貝,幸運的是java提供的大部分的容器類都實現了Cloneable介面。所以實現深拷貝並不是特別困難。

七. 原型設計模式的應用場景

7.1 原型模式適用的場景:

  1. 通常, 一個類很複雜的時候, 包含了很多種資料和結構,資料結構層次又比較深時,適用於原型模式。
  2. 當複雜的物件需要獨立於系統執行, 而不能破壞本系統中的結構時。

7.2 例項場景:

  1. 一個樓盤有名稱,地址和施工隊三個成員變數。施工隊有名稱,人數和包工頭。包工頭有名稱和年齡。現在要建設一個隔壁的樓盤,還是由這個施工隊進行建設的,只是地址不同。如果重新建立,過程較為複雜,費時費力,採取原型模式可以快速構建一個新的樓盤。
  2. 系統中已經有一架飛機,飛機有名稱和型號和廠商。廠商有名稱,地址和負責人。負責人有姓名和年齡。現在要一家相同的飛機由不同的負責人進行指導生產的,如何快速建立這樣的物件。

八. 原型設計模式運用了那幾大原則

我們來盤點一下設計模式的六大原則, 看看上面的原型模式案例應用了設計模式六大原則的哪幾類模型

  1. 單一職責原則:類很簡單, 符合
  2. 裡式替換原則:重寫了父類的clone方法,不符合
  3. 介面隔離原則:最小介面原則,符合
  4. 依賴倒置原則:不依賴於具體,依賴於抽象,介面隔離原則實現了cloneable介面,符合
  5. 迪米特法則:類之間有最好了聯絡。藉口隔離原則很簡單,也符合
  6. 開放封閉原則:對擴充套件開放,對修改關閉。符合

相關文章