設計模式--原型模式[轉載]

原型模式(Prototype),用原型實例指定建立對象的種類,而且經過拷貝這些原型建立新的對象html

本文轉載自 http://www.cnblogs.com/java-my-life/archive/2012/04/11/2439387.htmljava

爲了方便本身查看 這裏所有拷貝過來安全

在閻宏博士的《JAVA與模式》一書中開頭是這樣描述原型(Prototype)模式的:ide

  原型模式屬於對象的建立模式。經過給出一個原型對象來指明全部建立的對象的類型,而後用複製這個原型對象的辦法建立出更多同類型的對象。這就是選型模式的用意。函數


原型模式的結構

  原型模式要求對象實現一個能夠「克隆」自身的接口,這樣就能夠經過複製一個實例對象自己來建立一個新的實例。這樣一來,經過原型實例建立新的對 象,就再也不須要關心這個實例自己的類型,只要實現了克隆自身的方法,就能夠經過這個方法來獲取新的對象,而無須再去經過new來建立。post

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

簡單形式的原型模式

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

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

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

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

源代碼

  抽象原型角色

複製代碼
public interface Prototype{ /** * 克隆自身的方法 * @return 一個從自身克隆出來的對象 */ public Object clone(); }
複製代碼

  具體原型角色

複製代碼
public class ConcretePrototype1 implements Prototype { public Prototype clone(){ //最簡單的克隆,新建一個自身對象,因爲沒有屬性就再也不復制值了 Prototype prototype = new ConcretePrototype1(); return prototype; } }
複製代碼
複製代碼
public class ConcretePrototype2 implements Prototype { public Prototype clone(){ //最簡單的克隆,新建一個自身對象,因爲沒有屬性就再也不復制值了 Prototype prototype = new ConcretePrototype2(); return prototype; } }
複製代碼

  客戶端角色

複製代碼
public class Client { /** * 持有須要使用的原型接口對象 */ private Prototype prototype; /** * 構造方法,傳入須要使用的原型接口對象 */ public Client(Prototype prototype){ this.prototype = prototype; } public void operation(Prototype example){ //須要建立原型接口的對象 Prototype copyPrototype = prototype.clone(); } }
複製代碼

 

登記形式的原型模式

  

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

源代碼

  抽象原型角色

public interface Prototype{ public Prototype clone(); public String getName(); public void setName(String name); }

  具體原型角色

複製代碼
public class ConcretePrototype1 implements Prototype { private String name; public Prototype clone(){ ConcretePrototype1 prototype = new ConcretePrototype1(); prototype.setName(this.name); return prototype; } public String toString(){ return "Now in Prototype1 , name = " + this.name; } @Override public String getName() { return name; } @Override public void setName(String name) { this.name = name; } }
複製代碼
複製代碼
public class ConcretePrototype2 implements Prototype { private String name; public Prototype clone(){ ConcretePrototype2 prototype = new ConcretePrototype2(); prototype.setName(this.name); return prototype; } public String toString(){ return "Now in Prototype2 , name = " + this.name; } @Override public String getName() { return name; } @Override public void setName(String name) { this.name = name; } }
複製代碼

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

複製代碼
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; } }
複製代碼

  客戶端角色

複製代碼
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提供的克隆方法,都存在一個淺度克隆和深度克隆的問題。

  •   淺度克隆

  只負責克隆按值傳遞的數據(好比基本數據類型、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; } }
複製代碼

  運行結果:

  從運行的結果能夠看出,大聖的金箍棒和他的身外之身的金箍棒是不一樣的對象。這是由於使用了深克隆,從而把大聖本尊所引用的對象也都複製了一遍,其中也包括金箍棒。

  


 

原型模式的優勢

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

原型模式的缺點

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

相關文章
相關標籤/搜索