一天一個設計模式(四) - 原型模式(Prototype)

零壹技術棧發表於2018-07-13

前言

原型模式屬於物件的建立模式。通過給出一個原型物件來指明所有建立的物件的型別,然後用這個原型物件提供的複製辦法建立出更多同型別的物件。

原型模式的結構

原型模式要求物件實現一個可以克隆自身的介面(型別)。這樣一來,通過原型例項建立新的物件,就不需要關心這個例項本身的型別,只需要實現克隆自身的方法,也而無需再去通過new來建立。

原型型別的表現形式

  1. 簡單形式
  2. 登記形式

正文

簡單形式

一天一個設計模式(四) - 原型模式(Prototype)

相關角色

  1. 客戶(Client)角色客戶類提出建立物件的請求;
  2. 抽象原型(Prototype)角色:這是一個抽象角色,通常由一個Java介面或者Java抽象類實現。此角色定義了的具體原型類所需的實現的方法。
  3. 具體原型(Concrete Prototype)角色:此角色需要實現抽象原型角色要求的克隆相關介面

示例程式碼

Prototype.java

/**
 * 抽象原型角色
 */
public abstract class Prototype {
    private String id;

    public Prototype(String id) {
        this.id = id;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    /**
     * 克隆自身的方法
     * @return 一個從自身克隆出來的物件。
     */
    public abstract Prototype clone();
}
複製程式碼

ConcretePrototype1.java

public class ConcretePrototype1 extends Prototype {
    public ConcretePrototype1(String id) {
        super(id);
    }

    public Prototype clone() {
        Prototype prototype = new ConcretePrototype1(this.getId());
        return prototype;
    }
}
複製程式碼

ConcretePrototype2.java

public class ConcretePrototype2 extends Prototype {
    public ConcretePrototype2(String id) {
        super(id);
    }

    public Prototype clone() {
        Prototype prototype = new ConcretePrototype2(this.getId());
        return prototype;
    }
}
複製程式碼

執行結果

一天一個設計模式(四) - 原型模式(Prototype)

登記形式

一天一個設計模式(四) - 原型模式(Prototype)

相關角色

  1. 客戶(Client)角色客戶類提出建立物件的請求;
  2. 抽象原型(Prototype)角色:這是一個抽象角色,通常由一個Java介面或者Java抽象類實現。此角色定義了的具體原型類所需的實現的方法。
  3. 具體原型(Concrete Prototype)角色:此角色需要實現抽象原型角色要求的克隆相關介面
  4. 原型管理器(Prototype Manager)角色:提供各種原型物件建立管理

示例程式碼

除了原型管理器Prototype Manager以外,登記模式簡單模式並無其他差異。

Prototype.java W

public interface Prototype {
    public Prototype clone();
    public String getName();
    public void setName(String name);
}
複製程式碼

ConcretePrototype1.java

public class ConcretePrototype1 implements Prototype {
    private String name;

    @Override
    public String getName() {
        return this.name;
    }

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

    @Override
    public Prototype clone() {
        Prototype prototype = new ConcretePrototype1();
        prototype.setName(this.name);
        return prototype;
    }

    @Override
    public String toString() {
        return "ConcretePrototype1 [name=" + name + "]";
    }

}
複製程式碼

ConcretePrototype2.java

public class ConcretePrototype2 implements Prototype {
    private String name;

    @Override
    public String getName() {
        return this.name;
    }

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

    @Override
    public Prototype clone() {
        Prototype prototype = new ConcretePrototype2();
        prototype.setName(this.name);
        return prototype;
    }

    @Override
    public String toString() {
        return "ConcretePrototype2 [name=" + name + "]";
    }
}
複製程式碼

PrototypeManager.java

public class PrototypeManager {
    /**
     * 用來記錄原型的編號同原型例項的物件關係
     */
    private static Map<String, Prototype> map = new HashMap<>();

    /**
     * 私有化構造方法,避免從外部建立例項
     */
    private PrototypeManager() {
    }

    /**
     * 向原型管理器裡面新增或者修改原型例項
     *
     * @param prototypeId 原型編號
     * @param prototype   原型例項
     */
    public static void setProtoType(String prototypeId, Prototype prototype) {
        map.put(prototypeId, prototype);
    }

    /**
     * 根據原型編號從原型管理器裡面移除原型例項
     *
     * @param prototypeId 原型編號
     */
    public static void removePrototype(String prototypeId) {
        map.remove(prototypeId);
    }

    /**
     * 根據原型編號獲取原型例項
     *
     * @param prototypeId 原型編號
     * @return 原型例項物件
     * @throws Exception 如果根據原型編號無法獲取對應例項,則提示異常“您希望獲取的原型還沒有註冊或已被銷燬”
     */
    public static Prototype getPrototype(String prototypeId) throws Exception {
        Prototype prototype = map.get(prototypeId);

        if (prototype == null) {
            throw new Exception("您希望獲取的原型還沒有註冊或已被銷燬");
        }

        return prototype;
    }

}
複製程式碼

Client.java

public class Client {
    public static void main(String[] args) {
        try {
            // 建立第一個例項
            Prototype p1 = new ConcretePrototype1();
            // 註冊第一個例項
            PrototypeManager.setProtoType("p1", p1);

            // 克隆第一個例項的原型
            Prototype p3 = PrototypeManager.getPrototype("p1").clone();
            p3.setName("張三");
            System.out.println("第一個例項的副本:" + p3);

            // 建立第二個例項
            Prototype p2 = new ConcretePrototype2();
            // 註冊第二個例項
            PrototypeManager.setProtoType("p2", p2);

            // 克隆第二個例項的原型
            Prototype p4 = PrototypeManager.getPrototype("p2").clone();
            p4.setName("李四");
            System.out.println("第二個例項的副本:" + p4);

            // 登出第一個例項
            PrototypeManager.removePrototype("p1");
            // 再次克隆第一個例項的原型
            Prototype p5 = PrototypeManager.getPrototype("p1").clone();
            p5.setName("王五");
            System.out.println("第一個例項的副本:" + p5);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
複製程式碼

執行結果

一天一個設計模式(四) - 原型模式(Prototype)

兩者之間的比較

簡單形式和登記形式的原型模式各有其長處和短處。

  1. 如果要建立的原型物件資料較少而且比較固定的話,可以採用第一種形式。在這種情況下,原型物件的引用可以由客戶端自己儲存。
  2. 如果要建立的原型物件資料不固定的話,可以採用第二種形式。在這種情況下,客戶端不儲存對原型物件的引用,這個任務被交給原型管理器角色。在克隆一個物件之前,客戶端可以檢視管理員物件是否已經有一個滿足要求的原型物件。如果有,可以從原型管理器角色中取得這個物件引用;如果沒有,客戶端就需要自行復制此原型物件。

總結

原型模式的優點

原型模式允許在執行時動態改變具體的實現型別。原型模式可以在執行期間,有客戶來註冊符合原型介面的實現型別,也可以動態的改變具體的實現型別,看起來介面沒有任何變化,但是其實執行的已經是另外一個類實體了。因為克隆一個原型物件就類似於例項化一個類

原型模式的缺點

原型模式最主要的缺點是每一個類都必須要配備一個克隆方法。配備克隆方法需要對類的功能進行通盤考慮,這對於全新的類來說並不是很難,但是對於已有的類來說並不容易。


歡迎關注技術公眾號: 零壹技術棧

零壹技術棧

本帳號將持續分享後端技術乾貨,包括虛擬機器基礎,多執行緒程式設計,高效能框架,非同步、快取和訊息中介軟體,分散式和微服務,架構學習和進階等學習資料和文章。

相關文章