《JAVA與模式》26天系列—第6天—原型模式

來源:互聯網
上載者:User
文章目錄
  • 登記形式的原型模式
  • 兩種形式的比較

 原型模式屬於對象的建立模式。通過給出一個原型對象來指明所有建立的對象的類型,然後用複製這個原型對象的辦法建立出更多同類型的對象。這就是原型模式的用意。

原型模式的結構

  原型模式要求對象實現一個可以“複製”自身的介面,這樣就可以通過複製一個執行個體對象本身來建立一個新的執行個體。這樣一來,通過原型執行個體建立新的對象,就不再需要關心這個執行個體本身的類型,只要實現了複製自身的方法,就可以通過這個方法來擷取新的對象,而無須再去通過new來建立。

  原型模式有兩種表現形式:(1)簡單形式、(2)登記形式,這兩種表現形式僅僅是原型模式的不同實現。  

簡單形式的原型模式

這種形式涉及到三個角色:

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

  (2)抽象原型(Prototype)角色:這是一個抽象角色,通常由一個Java介面或Java抽象類別實現。此角色給出所有的具體原型類所需的介面。

  (3)具體原型(Concrete Prototype)角色:被複製的對象。此角色需要實現抽象的原型角色所要求的介面。

原始碼

  抽象原型角色

 

package com.bankht.Prototype;/** * @author: 特種兵—AK47 * @建立時間:2012-6-25 下午03:58:33 *  * @類說明 :抽象原型角色 */public interface Prototype {/** * 複製自身的方法 *  * @return 一個從自身複製出來的對象 */public Prototype clone();}

 

具體原型角色
 

package com.bankht.Prototype;/** * @author: 特種兵—AK47 * @建立時間:2012-6-25 下午03:59:15 *  * @類說明 :具體原型角色 */public class ConcretePrototype1 implements Prototype {public Prototype clone() {// 最簡單的複製,建立一個自身對象,由於沒有屬性就不再複製值了Prototype prototype = new ConcretePrototype1();return prototype;}}
package com.bankht.Prototype;/** * @author: 特種兵—AK47 * @建立時間:2012-6-25 下午03:59:15 *  * @類說明 :具體原型角色 */public class ConcretePrototype2 implements Prototype {public Prototype clone() {// 最簡單的複製,建立一個自身對象,由於沒有屬性就不再複製值了Prototype prototype = new ConcretePrototype2();return prototype;}}

  用戶端角色

package com.bankht.Prototype;import org.junit.Test;/** * @author: 特種兵—AK47 * @建立時間:2012-6-25 下午04:00:46 *  * @類說明 :用戶端角色 */public class Client {@Testpublic void testPrototype() {new testPrototype(new ConcretePrototype1()).operation();}}class testPrototype {/** * 持有需要使用的原型介面對象 */public Prototype prototype;/** * 構造方法,傳入需要使用的原型介面對象 */public testPrototype(Prototype prototype) {this.prototype = prototype;}public void operation() {// 需要建立原型介面的對象System.out.println("擷取的原型為:"+this.prototype.clone().getClass().getSimpleName()+".java");}}

 

運行結果:

擷取的原型為:ConcretePrototype1.java

 

登記形式的原型模式

  

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

原始碼

  抽象原型角色

package com.bankht.Prototype.register;/** * @author: 特種兵—AK47 * @建立時間:2012-6-25 下午04:53:29 *  * @類說明 :抽象原型角色 */public interface Prototype {public Prototype clone();public String getName();public void setName(String name);}

 

  具體原型角色

package com.bankht.Prototype.register;/** * @author: 特種兵—AK47 * @建立時間:2012-6-25 下午04:54:03 *  * @類說明 :具體原型角色 */public class ConcretePrototype1 implements Prototype {private String name;public Prototype clone() {ConcretePrototype1 prototype = new ConcretePrototype1();return prototype;}public String toString() {return "Now in Prototype1 , name = " + this.name;}@Overridepublic String getName() {return name;}@Overridepublic void setName(String name) {this.name = name;}}

 

package com.bankht.Prototype.register;/** * @author: 特種兵—AK47 * @建立時間:2012-6-25 下午04:54:29 *  * @類說明 :具體原型角色 */public class ConcretePrototype2 implements Prototype {private String name;public Prototype clone() {ConcretePrototype2 prototype = new ConcretePrototype2();return prototype;}public String toString() {return "Now in Prototype2 , name = " + this.name;}@Overridepublic String getName() {return name;}@Overridepublic void setName(String name) {this.name = name;}}

 

  原型管理器角色保持一個聚集,作為對所有原型對象的登記,這個角色提供必要的方法,供外界增加新的原型對象和取得已經登記過的原型對象。

package com.bankht.Prototype.register;import java.util.HashMap;import java.util.Map;/** * @author: 特種兵—AK47 * @建立時間:2012-6-25 下午04:54:55 *  * @類說明 :原型管理器角色保持一個聚集,作為對所有原型對象的登記,這個角色提供必要的方法,供外界增加新的原型對象和取得已經登記過的原型對象。 */public class PrototypeManager {/** * 用來記錄原型的編號和原型執行個體的對應關係 */private static Map<String, Prototype> map = new HashMap<String, Prototype>();/** * 私人化構造方法,避免外部建立執行個體 */private PrototypeManager() {}/** * 向原型管理器裡面添加或是修改某個原型註冊 *  * @param prototypeId *            原型編號 * @param prototype *            原型執行個體 */public synchronized static void setPrototype(String prototypeId, Prototype prototype) {map.put(prototypeId, prototype);}/** * 從原型管理器裡面刪除某個原型註冊 *  * @param prototypeId *            原型編號 */public synchronized static void removePrototype(String prototypeId) {map.remove(prototypeId);}/** * 擷取某個原型編號對應的原型執行個體 *  * @param prototypeId *            原型編號 * @return 原型編號對應的原型執行個體 * @throws Exception *             如果原型編號對應的執行個體不存在,則拋出異常 */public synchronized static Prototype getPrototype(String prototypeId) throws Exception {Prototype prototype = map.get(prototypeId);if (prototype == null) {throw new Exception("您希望擷取的原型還沒有註冊或已被銷毀");}return prototype;}}

 用戶端角色

package com.bankht.Prototype.register;/** * @author: 特種兵—AK47 * @建立時間:2012-6-25 下午04:55:39 *  * @類說明 : 用戶端角色 */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("p1", p2);// 重新擷取原型來建立對象Prototype p4 = PrototypeManager.getPrototype("p1").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();}}}
兩種形式的比較

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

  如果需要建立的原型對象數目較少而且比較固定的話,可以採取第一種形式。在這種情況下,原型對象的引用可以由用戶端自己儲存。

  如果要建立的原型對象數目不固定的話,可以採取第二種形式。在這種情況下,用戶端不儲存對原型對象的引用,這個任務被交給管理員對象。在複製一個原型對象之前,用戶端可以查看管理員對象是否已經有一個滿足要求的原型對象。如果有,可以直接從管理員類取得這個對象引用;如果沒有,用戶端就需要自行複製此原型對象。

 

Java中的複製方法

  Java的所有類都是從java.lang.Object類繼承而來的,而Object類提供protected Object clone()方法對對象進行複製,子類當然也可以把這個方法置換掉,提供滿足自己需要的複製方法。對象的複製有一個基本問題,就是對象通常都有對其他的對象的引用。當使用Object類的clone()方法來複製一個對象時,此對象對其他對象的引用也同時會被複製一份

  Java語言提供的Cloneable介面只起一個作用,就是在運行時期通知Java虛擬機器可以安全地在這個類上使用clone()方法。通過調用這個clone()方法可以得到一個對象的複製。由於Object類本身並不實現Cloneable介面,因此如果所考慮的類沒有實現Cloneable介面時,調用clone()方法會拋出CloneNotSupportedException異常。

複製滿足的條件

  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()方法時,也應當遵守著三個條件。一般來說,上面的三個條件中的前兩個是必需的,而第三個是可選的。

淺複製和深複製

  無論你是自己實現複製方法,還是採用Java提供的複製方法,都存在一個淺度複製和深度複製的問題。

  •   淺度複製

  只負責複製按值傳遞的資料(比如基礎資料型別 (Elementary Data Type)、String類型),而不複製它所引用的對象,換言之,所有的對其他對象的引用都仍然指向原來的對象。

  •   深度複製

  除了淺度複製要複製的值外,還負責複製參考型別的資料。那些引用其他對象的變數將指向被複製過的新對象,而不再是原有的那些被引用的對象。換言之,深度複製把要複製的對象所引用的對象都複製了一遍,而這種對被引用到的對象的複製叫做間接複製。

  深度複製要深入到多少層,是一個不易確定的問題。在決定以深度複製的方式複製一個對象的時候,必須決定對間接複製的對象時採取淺度複製還是繼續採用深度複製。因此,在採取深度複製時,需要決定多深才算深。此外,在深度複製的過程中,很可能會出現循環參考的問題,必須小心處理。

利用序列化實現深度複製

  把對象寫到流裡的過程是序列化(Serialization)過程;而把對象從流中讀出來的過程則叫還原序列化(Deserialization)過程。應當指出的是,寫到流裡的是對象的一個拷貝,而原對象仍然存在於JVM裡面。

  在Java語言裡深度複製一個對象,常常可以先使對象實現Serializable介面,然後把對象(實際上只是對象的拷貝)寫到一個流裡(序列化),再從流裡讀回來(還原序列化),便可以重建對象。

public  Object deepClone() throws IOException, ClassNotFoundException{        //將對象寫到流裡        ByteArrayOutputStream bos = new ByteArrayOutputStream();        ObjectOutputStream oos = new ObjectOutputStream(bos);        oos.writeObject(this);        //從流裡讀回來        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());        ObjectInputStream ois = new ObjectInputStream(bis);        return ois.readObject();    }

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

  淺度複製顯然比深度複製更容易實現,因為Java語言的所有類都會繼承一個clone()方法,而這個clone()方法所做的正式淺度複製。

  有一些對象,比如線程(Thread)對象或Socket對象,是不能簡單複製或共用的。不管是使用淺度複製還是深度複製,只要涉及這樣的間接對象,就必須把間接對象設成transient而不予複製;或者由程式自行建立出相當的同種對象,權且當做複製件使用。

  

 

孫大聖的身外身法術

  孫大聖的身外身本領如果在Java語言裡使用原型模式來實現的話,會怎麼樣呢?首先,齊天大聖(The Greatest Sage)即TheGreatestSage類扮演客戶角色。齊天大聖持有一個猢猻(Monkey)的執行個體,而猢猻就是大聖本尊。Monkey類具有繼承自java.lang.Object的clone()方法,因此,可以通過調用這個複製方法來複製一個Monkey執行個體。

  孫大聖本人用TheGreatestSage類代表

public class TheGreatestSage {    private Monkey monkey = new Monkey();        public void change(){        //複製大聖本尊        Monkey copyMonkey = (Monkey)monkey.clone();        System.out.println("大聖本尊的生日是:" + monkey.getBirthDate());        System.out.println("複製的大聖的生日是:" + monkey.getBirthDate());        System.out.println("大聖本尊跟複製的大聖是否為同一個對象 " + (monkey == copyMonkey));        System.out.println("大聖本尊持有的金箍棒 跟 複製的大聖持有的金箍棒是否為同一個對象? " + (monkey.getStaff() == copyMonkey.getStaff()));    }        public static void main(String[]args){        TheGreatestSage sage = new TheGreatestSage();        sage.change();    }}

  大聖本尊由Monkey類代表,這個類扮演具體原型角色:

public class Monkey implements Cloneable {    //身高    private int height;    //體重    private int weight;    //生日    private Date birthDate;    //金箍棒    private GoldRingedStaff staff;    /**     * 建構函式     */    public Monkey(){        this.birthDate = new Date();        this.staff = new GoldRingedStaff();    }    /**     * 複製方法     */    public Object clone(){        Monkey temp = null;        try {            temp = (Monkey) super.clone();        } catch (CloneNotSupportedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        } finally {            return temp;        }    }    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 Date getBirthDate() {        return birthDate;    }    public void setBirthDate(Date birthDate) {        this.birthDate = birthDate;    }    public GoldRingedStaff getStaff() {        return staff;    }    public void setStaff(GoldRingedStaff staff) {        this.staff = staff;    }    }

  大聖還持有一個金箍棒的執行個體,金箍棒類GoldRingedStaff:

public class GoldRingedStaff {    private float height = 100.0f;    private float diameter = 10.0f;    /**     * 增長行為,每次調用長度和半徑增加一倍     */    public void grow(){        this.diameter *= 2;        this.height *= 2;    }    /**     * 縮小行為,每次調用長度和半徑減少一半     */    public void shrink(){        this.diameter /= 2;        this.height /= 2;    }}

  當運行TheGreatestSage類時,首先建立大聖本尊對象,而後淺度複製大聖本尊對象。程式在運行時列印出的資訊如下:

 可以看出,首先,複製的大聖本尊具有和原始的大聖本尊對象一樣的birthDate,而本尊對象不相等,這表明他們二者是複製關係;其次,複製的大聖本尊所持有的金箍棒和原始的大聖本尊所持有的金箍棒為同一個對象。這表明二者所持有的金箍棒根本是一根,而不是兩根。

  正如前面所述,繼承自java.lang.Object類的clone()方法是淺複製。換言之,齊天大聖的所有化身所持有的金箍棒引用全都是指向一個對象的,這與《西遊記》中的描寫並不一致。要糾正這一點,就需要考慮使用深複製

  為做到深度複製,所有需要複製的對象都需要實現java.io.Serializable介面。

  孫大聖的原始碼:

public class TheGreatestSage {    private Monkey monkey = new Monkey();        public void change() throws IOException, ClassNotFoundException{        Monkey copyMonkey = (Monkey)monkey.deepClone();        System.out.println("大聖本尊的生日是:" + monkey.getBirthDate());        System.out.println("複製的大聖的生日是:" + monkey.getBirthDate());        System.out.println("大聖本尊跟複製的大聖是否為同一個對象 " + (monkey == copyMonkey));        System.out.println("大聖本尊持有的金箍棒 跟 複製的大聖持有的金箍棒是否為同一個對象? " + (monkey.getStaff() == copyMonkey.getStaff()));    }        public static void main(String[]args) throws IOException, ClassNotFoundException{        TheGreatestSage sage = new TheGreatestSage();        sage.change();    }}

 

  在大聖本尊Monkey類裡面,有兩個複製方法,一個是clone(),也即淺複製;另一個是deepClone(),也即深複製。在深複製方法裡,大聖本尊對象(一個拷貝)被序列化,然後又被還原序列化。還原序列化的對象就成了一個深複製的結果。

  

public class Monkey implements Cloneable,Serializable {    //身高    private int height;    //體重    private int weight;    //生日    private Date birthDate;    //金箍棒    private GoldRingedStaff staff;    /**     * 建構函式     */    public Monkey(){        this.birthDate = new Date();        staff = new GoldRingedStaff();    }    /**     * 複製方法     */    public Object clone(){        Monkey temp = null;        try {            temp = (Monkey) super.clone();        } catch (CloneNotSupportedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        } finally {            return temp;        }    }    public  Object deepClone() throws IOException, ClassNotFoundException{        //將對象寫到流裡        ByteArrayOutputStream bos = new ByteArrayOutputStream();        ObjectOutputStream oos = new ObjectOutputStream(bos);        oos.writeObject(this);        //從流裡讀回來        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());        ObjectInputStream ois = new ObjectInputStream(bis);        return ois.readObject();    }    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 Date getBirthDate() {        return birthDate;    }    public void setBirthDate(Date birthDate) {        this.birthDate = birthDate;    }    public GoldRingedStaff getStaff() {        return staff;    }    public void setStaff(GoldRingedStaff staff) {        this.staff = staff;    }    }

 

  可以看到,大聖本尊持有一個金箍棒(GoldRingedStaff)的執行個體。在大聖複製件裡面,此金箍棒執行個體是原大聖本尊對象所持有的金箍棒對象的一個拷貝。在大聖本尊對象被序列化和還原序列化時,它所持有的金箍棒對象也同時被序列化和還原序列化,這使得複製的大聖的金箍棒和原大聖本尊對象所持有的金箍棒對象是兩個獨立的對象。

public class GoldRingedStaff implements Serializable{    private float height = 100.0f;    private float diameter = 10.0f;    /**     * 增長行為,每次調用長度和半徑增加一倍     */    public void grow(){        this.diameter *= 2;        this.height *= 2;    }    /**     * 縮小行為,每次調用長度和半徑減少一半     */    public void shrink(){        this.diameter /= 2;        this.height /= 2;    }}

  從啟動並執行結果可以看出,大聖的金箍棒和他的身外之身的金箍棒是不同的對象。這是因為使用了深複製,從而把大聖本尊所引用的對象也都複製了一遍,其中也包括金箍棒。

  

 

 

原型模式的優點

  原型模式允許在運行時動態改變具體的實作類別型。原型模式可以在運行期間,由客戶來註冊符合原型介面的實作類別型,也可以動態地改變具體的實作類別型,看起來介面沒有任何變化,但其實啟動並執行已經是另外一個類執行個體了。因為複製一個原型就類似於執行個體化一個類。

原型模式的缺點

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

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.