原始(Prototype)模式

  原型模式:用原型實例指定建立對象的種類,而且經過拷貝這些原型對象建立新的對象。java

  原型模式其實就是從一個對象建立另一個可複製的對象,並且不須要知道任何建立的細節。(最經常使用的就是基於流的深複製)ide

 原始模型模式

   Java語言自己支持原始原型模式。全部的JavaBean都繼承自Java.lang.Object,而Object類提供一個clone方法,能夠將一個JavaBean對象複製一份。可是這個JavaBean必須實現一個表示接口Cloneable,代表這個JavaBean支持複製。若是一個對象沒有實現這個接口而調用clone()方法,Java編譯器會拋出CloneNotSupportedException異常。測試

變量、對象以及對象的引用

  對象就是類的實例。通常狀況下,一個類被實例化時,此類的全部成員,包括變量和方法,都被複制到屬於此數據類型的一個新的實例中取。好比:this

User user = new User();

上面的語句作了以下的事情:spa

(1)建立了一個User類型的變量,稱爲userprototype

(2)創建了一個User類的對象線程

(3)使變量user指到這個對象上。設計

能夠將上面分紅下面兩步:code

        User user;
        user = new User();

  第一行創建了一個變量叫作user,能夠指到User類對象上。但在上面第一行結束時並無指到它上面,只是在第二行才真正指到這樣的一個對象上。對象

  第二行建立了一個對象。當對象剛被建立時是一個無名對象,隨後這個對象纔有了一個名字user。

  也有可能一個對象被建立以後就沒有名字,永遠保持無名狀態。

        User user = new User();
        user.setBirthDay(new Date());

   如上面,咱們知道參數的傳遞是經過引用傳遞,因此只是將地址傳下去。接受參數的setBirthDay 方法也不關心它接收的對象有沒有名字。

   所以,對象的建立與它們的引用是獨立的

克隆知足的條件

  clone()方法將對象複製了一份並返還給調用者。所謂「複製」的含義與clone()方法是怎麼實現的。通常而言,clone()方法知足如下的描述:

  (1)對任何的對象x,都有:x.clone()!=x。換言之,克隆對象與原對象不是同一個對象。

  (2)對任何的對象x,都有:x.clone().getClass() == x.getClass(),換言之,克隆對象與原對象的類型同樣。

  (3)若是對象x的equals()方法定義其恰當的話,那麼x.clone().equals(x)應當成立的。

  在JAVA語言的API中,凡是提供了clone()方法的類,都知足上面的這些條件。JAVA語言的設計師在設計本身的clone()方法時,也應當遵照着三個條件。通常來講,上面的三個條件中的前兩個是必需的,而第三個是可選的。

原始模式結構:

  原型模式有兩種表現形式:(1)簡單形式;(2)登記形式。

(1)簡單形式:

三個角色:

  (1)客戶(Client)角色:客戶類提出建立對象的請求。

  (2)抽象原型(Prototype)角色:這是一個抽象角色,一般由一個Java接口或Java抽象類實現。此角色給出全部的具體原型類所需的接口。

  (3)具體原型(Concrete Prototype)角色:被複制的對象。此角色須要實現抽象的原型角色所要求的接口。

以下:

package cn.qlq.prototype;

public interface Prototype {

    Prototype clone();
}
package cn.qlq.prototype;

public class ConcretePrototype1 implements Prototype {

    public Prototype clone() {
        // 沒有屬性就再也不復制值了
        Prototype prototype = new ConcretePrototype1();

        return prototype;
    }

}
package cn.qlq.prototype;

public class ConcretePrototype2 implements Prototype {

    public Prototype clone() {
        // 沒有屬性就再也不復制值了
        Prototype prototype = new ConcretePrototype2();
        return prototype;
    }

}

 

客戶端代碼:

package cn.qlq.prototype;

public class Client {

    private Prototype prototype;

    public Client(Prototype prototype) {
        this.prototype = prototype;
    }

    public void operation(Prototype example) {
        Prototype copyPrototype = prototype.clone();
    }

}

 

(2)登記形式

   做爲原型模式的第二種形式,它比簡單形式多了一個原型管理器(PrototypeManager)角色,該角色的做用是:建立具體原型類的對象,並記錄每個被建立的對象。

   原型管理器角色保持一個集合,做爲對全部原型對象的登記,這個角色提供必要的方法,供外界增長新的原型對象和取得已經登記過的原型對象。

package cn.qlq.prototype;

import java.util.HashMap;
import java.util.Map;

public class PrototypeManager {

    private static Map<String, Prototype> prototypes = new HashMap<String, Prototype>();

    private PrototypeManager() {
    }

    public synchronized static void setPrototype(String prototypeId, Prototype prototype) {
        prototypes.put(prototypeId, prototype);
    }

    public synchronized static void removePrototype(String prototypeId) {
        prototypes.remove(prototypeId);
    }

    public synchronized static Prototype getPrototype(String prototypeId) throws Exception {
        Prototype prototype = prototypes.get(prototypeId);
        if (prototype == null) {
            throw new Exception("不存在");
        }

        return prototype;
    }
}

 

測試代碼:

package cn.qlq.prototype;

public class Client {

    public static void main(String[] args) throws Exception {
        Prototype p1 = new ConcretePrototype1();
        PrototypeManager.setPrototype("p1", p1);
        // 獲取原型來建立對象而且複製一份
        Prototype p3 = PrototypeManager.getPrototype("p1").clone();
        System.out.println(p1 == p3);
    }

}

兩種形式的比較:

  若是須要建立的原型對象數目較少並且固定的話,能夠採起第一種形式也就是簡單形式的原始模型模式。在這種狀況下,原型對象的引用由客戶端本身保存。

  若是要建立的原型對象數目不固定的話,採用登記形式。保存原對象的引用由管理器角色執行。在複製一個原型對象以前,客戶端能夠查看管理員對象是否已經有一個知足要求的的原型對象,若是有直接返回,若是沒有客戶端就須要自行復制此原型對象。

 

淺複製和深複製:

淺複製
  只負責克隆按值傳遞的數據(好比基本數據類型、String類型),而不復制它所引用的對象,換言之,全部的對其餘對象的引用都仍然指向原來的對象。

深複製
  除了淺度克隆要克隆的值外,還負責克隆引用類型的數據。那些引用其餘對象的變量將指向被複制過的新對象,而再也不是原有的那些被引用的對象。換言之,深度克隆把要複製的對象所引用的對象都複製了一遍,而這種對被引用到的對象的複製叫作間接複製。

  深度克隆要深刻到多少層,是一個不易肯定的問題。在決定以深度克隆的方式複製一個對象的時候,必須決定對間接複製的對象時採起淺度克隆仍是繼續採用深度克隆。所以,在採起深度克隆時,須要決定多深纔算深。此外,在深度克隆的過程當中,極可能會出現循環引用的問題,必須當心處理。

利用串行化實現深度克隆:(重要)

  把對象寫到流裏的過程稱爲串行化,在Java中又稱爲"冷凍"或者"醃鹹菜";而把對象從流裏讀出來的並行化過程則叫作"解凍"或者"回鮮"過程。應當指出的是,寫到流裏的是對象的一個拷貝,而原對象仍然存在於JVM裏面,所以"醃鹹菜"只是對象的一個拷貝。

  在Java語言裏深度克隆一個對象,經常能夠先使對象實現Serializable接口,而後把對象(實際上只是對象的拷貝)寫到一個流裏(序列化),再從流裏讀回來(反序列化),即可以重建對象。

以下代碼:

     public static <T> T cloneObj(T obj) {
            T retVal = null;

            try {
                // 將對象寫入流中
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(baos);
                oos.writeObject(obj);

                // 從流中讀出對象
                ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
                ObjectInputStream ois = new ObjectInputStream(bais);

                retVal = (T) ois.readObject();
            } catch (Exception e) {
                e.printStackTrace();
            }

            return retVal;
        }

  這樣作的前提就是對象以及對象內部全部引用到的對象都是可序列化的,不然,就須要仔細考察那些不可序列化的對象能否設成transient,從而將之排除在複製過程以外。

  淺複製顯然比深複製更容易實現,由於Java語言的全部類都會繼承一個clone()方法,而這個clone()方法所作的就是淺複製。

  有一些對象,好比線程(Thread)對象或Socket對象,是不能簡單複製或共享的。不論是使用淺複製仍是深複製,只要涉及這樣的間接對象,就必須把間接對象設成transient而不予複製;或者由程序自行建立出至關的同種對象,權且當作複製件使用。

 

以下測試代碼:

  User是根對象,其內部引用類也要實現Serializable接口,user的name屬性用transient關鍵字修飾不會被序列化。

package cn.qlq.prototype;

import java.io.Serializable;

public class User implements Serializable {

    private Address address;

    private int id;

    private transient String name;
    private String sex;
    private String job;
    private String health;
    private String BMI;
    private int height;
    private int weight;

    public User() {
        super();
    }

    public User(Address address, int id, String name, String sex, String job, String health, String bMI, int height,
            int weight) {
        super();
        this.address = address;
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.job = job;
        this.health = health;
        BMI = bMI;
        this.height = height;
        this.weight = weight;
    }

    public int getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }

    public String getHealth() {
        return health;
    }

    public void setHealth(String health) {
        this.health = health;
    }

    public String getBMI() {
        return BMI;
    }

    public void setBMI(String bMI) {
        BMI = bMI;
    }

    public int getHeight() {
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public int getWeight() {
        return weight;
    }

    public void setWeight(int weight) {
        this.weight = weight;
    }

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "User [address=" + address + ", id=" + id + ", name=" + name + ", sex=" + sex + ", job=" + job
                + ", health=" + health + ", BMI=" + BMI + ", height=" + height + ", weight=" + weight + "]";
    }

}

class Address implements Serializable {

    private String province;

    private String country;

    public Address(String province, String country) {
        super();
        this.province = province;
        this.country = country;
    }

    public String getProvince() {
        return province;
    }

    public void setProvince(String province) {
        this.province = province;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    @Override
    public String toString() {
        return "Address [province=" + province + ", country=" + country + "]";
    }

}
    public static void main(String[] args) throws Exception {
        User user = new User();
        user.setName("張三");
        user.setBMI("BMI");
        user.setHealth("健康");
        user.setHeight(50);
        user.setId(1);
        Address address = new Address("山西", "cn");
        user.setAddress(address);

        // 克隆對象並修改屬性後打印
        User cloneedUser = cloneObj(user);
        cloneedUser.getAddress().setCountry("北京");
        System.out.println(cloneedUser);

        System.out.println("========");
        System.out.println(user);
    }

結果:(name屬性沒有被複制上,並且其address是深複製)

User [address=Address [province=山西, country=北京], id=1, name=null, sex=null, job=null, health=健康, BMI=BMI, height=50, weight=0]
========
User [address=Address [province=山西, country=cn], id=1, name=張三, sex=null, job=null, health=健康, BMI=BMI, height=50, weight=0]

 

原型模式的優勢

  原型模式容許在運行時動態改變具體的實現類型。原型模式能夠在運行期間,由客戶來註冊符合原型接口的實現類型,也能夠動態地改變具體的實現類型,看起來接口沒有任何變化,但其實運行的已是另一個類實例了。由於克隆一個原型就相似於實例化一個類。

原型模式的缺點

  原型模式最主要的缺點是每個類都必須配備一個克隆方法。配備克隆方法須要對類的功能進行通盤考慮,這對於全新的類來講不是很難,而對於已經有的類不必定很容易,特別是當一個類引用不支持序列化的間接對象,或者引用含有循環結構的時候。

 

適用場景: 

  假設一個系統的產品類是動態加載的,並且產品具備必定的等級結構。這個時候若是使用工廠模式的話,工廠模式就得具備必定的結構。而產品類的等級結構發生變化,工廠類的等級結構就得隨之變化。這對於產品結構常常可能會變的系統來講,採用工廠模式就不方便。

  這時候就能夠採用原型模式,給每一個產品裝配一個克隆方法(大多數時候只需給產品類的根類裝配克隆方法)。

 

總結:

  這種模式使用的最多的就是深複製,項目中也沒用到這種模式。

相關文章
相關標籤/搜索