Java設計模式:Prototype(原型)模式

概念定義

使用原型實例指定待建立對象的種類,並經過拷貝該原型來建立新的對象。Prototype模式容許一個原型對象克隆(複製)出多個與其相同的對象,而無需知道任何如何建立的細節。java

應用場景

  • 對象的建立過程較爲複雜且須要頻繁建立
  • 指望根據現有的實例來生成新的實例,例如:
    • 對象種類繁多而沒法整合到一個類時
    • 難以經過指定類名生成實例時
    • 但願解耦框架與生成的實例時

在實際應用中,Prototype模式不多單獨出現。常常與其餘模式混用。數組

原型實現

全部Java類都繼承自java.lang.Object,而Object類提供clone()方法來克隆對象。所以,Java類實現Cloneable接口並重寫clone()方法後,便可實現Prototype模式框架

實現Prototype模式的示例代碼以下:ide

// 原型類(也可定義爲interface Prototype extends Cloneable)
abstract class Prototype implements Cloneable {
    private String name;
    public void setName(String name) {this.name = name;}
    public String getName() {return this.name;}

    public abstract void doSomething();

    @Override
    public Object clone() { // clone()方法用public修飾
        Object object = null;
        try {
            object = super.clone(); // 調用父類clone()方法
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return object;
    }
}

// 具體實現類(繼承自Prototype類自動具備克隆功能)
class ConcretePrototype extends Prototype{
    public ConcretePrototype() { setName("ConcretePrototype"); }

    @Override
    public void doSomething() { System.out.println("I'll clone myself!"); }
}

public class PrototypeDemo {
    public static void main(String[] args) {
        ConcretePrototype cp = new ConcretePrototype();
        cp.doSomething();

        for(int i=0; i< 5; i++){
            ConcretePrototype cloned = (ConcretePrototype)cp.clone();
            System.out.println("I'm cloned by " + cloned.getName());
        }
    }
}

執行後輸出以下:性能

I'll clone myself!
I'm cloned by ConcretePrototype
I'm cloned by ConcretePrototype
I'm cloned by ConcretePrototype
I'm cloned by ConcretePrototype
I'm cloned by ConcretePrototype

模式優缺點

Prototype模式的優勢以下:this

  • 根據客戶端要求實現運行時動態建立對象,客戶端不須要知道對象的建立細節,便於代碼的維護和擴展。
  • 當對象的建立較爲複雜或重複建立大量類似對象時,可簡化對象建立,且性能比直接new對象更高(new會自動調用構造鏈中的全部構造方法,但clone不會調用任何類構造方法)。
  • Prototype模式相似工廠模式,但沒有工廠模式中的抽象工廠和具體工廠的層級關係,代碼結構更清晰和簡單。

缺點以下:prototype

  • 須要爲每一個類實現Cloneable接口並重寫clone()方法,改造已有類時必須修改其源碼,違背"開閉原則"。
  • 單例模式與工廠模式、Prototype模式是衝突的,儘可能不要混用。
  • 在實現深拷貝(深克隆)時須要編寫較爲複雜的代碼。

注意事項

clone()方法只拷貝對象中的基本數據類型,而不會拷貝數組、容器對象、引用對象等。若要實現深拷貝,必須將Prototype模式中的數組、容器對象、引用對象等另行拷貝
例如,深拷貝一個對象時,該對象必須實現Cloneable接口並重寫clone()方法,並在clone()方法內部將該對象引用的其餘對象也克隆一份。同理,被引用的對象也要作一樣的處理。
注意,Boolean、Byte、Character、Class、Double、Float、Integer、Long、Short、String以及大部分的Exception的子類均爲不可變類,其對象沒有必要實現深拷貝。此外,大部分的Java容器類都已實現克隆功能。code

相較而言,clone()方法更適合數組深拷貝。但須要注意如下兩點:對象

(1) 基本類型的一維數組可經過諸如(int[])data.clone()的形式克隆,而基本類型的"二維數組"(其實是類型諸如int[]的一維數組)須要按維克隆。例如:繼承

public static int[][] twoDimensionArrayClone (int[][] tdArray){
    int[][] copy = tdArray.clone();
    for (int i = 0; i < tdArray.length; i++) {
        copy[i] = tdArray[i].clone();
    }
    return copy;
}

(2) 當數組元素爲普通Java對象時,克隆數組後還要克隆數組中包含的對象。如下示例詳細展現了數組對象的不一樣淺拷貝和深拷貝方式:

class Person implements Cloneable {
    public String name;
    public Person(String name) {this.name = name;}

    @Override
    public Object clone() {
        Object object = null;
        try {
            object = super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return object;
    }
}

public class ArrayPrototype {
    public static void main(String[] args) {
        Person[] origin = new Person[] { new Person("Mike"), new Person("Jack"), new Person("Jason") };
        Person[] copyOf = Arrays.copyOf(origin, origin.length); // 淺拷貝(內部調用System.arraycopy,返回新數組)
        Person[] arrayCopy = new Person[origin.length]; // 淺拷貝(可拷貝部分數組)
        System.arraycopy(origin, 0, arrayCopy, 0, origin.length);
        Person[] clonedCopy = origin.clone(); // 淺拷貝
        System.out.println("origin=copyOf=arrayCopy=clonedCopy=" +
                (origin[0] == copyOf[0] && copyOf[1] == arrayCopy[1] && arrayCopy[2] == clonedCopy[2]));
        clonedCopy[0].name = "Lily";
        System.out.println("Shallow Person[0]: " + origin[0].name + " -> " + clonedCopy[0].name);

        Person[] deepCopy = new Person[origin.length]; // 深拷貝
        for(int i = 0; i < origin.length; i++) { deepCopy[i] = (Person) origin[i].clone(); }
        deepCopy[1].name = "Lily";
        System.out.println("Deep Person[1]: " + origin[1].name + " -> " + clonedCopy[1].name);
        Person[] deepCopy2 = Arrays.stream(origin).map(Person::clone).toArray(Person[]::new); // 深拷貝
        deepCopy2[2].name = "Lucy";
        System.out.println("Deep Person[2]: " + origin[2].name + " -> " + deepCopy2[2].name);
        //origin=copyOf=arrayCopy=clonedCopy=true
        //Shallow Person[0]: Lily -> Lily
        //Deep Person[1]: Jack -> Jack
        //Deep Person[2]: Jason -> Lucy
    }
}

業界實踐

  • Action對象(Struts2)
  • prototype建立的bean實例(Spring)
相關文章
相關標籤/搜索