天天看點

Java設計模式—原型模式(prototype pattern)

原型模式是一種建立型設計模式,通過給出一個原型對象來指明所有建立的對象的類型,然後用複制這個原型對象的辦法建立出更多同類型的對象。

Java設計模式—原型模式(prototype pattern)

原型模式要求對象實作一個可以“克隆”自身的接口,該接口通過複制一個執行個體對象本身來建立一個新的執行個體。那麼原型執行個體建立新的對象時就無需關心這個執行個體本身的類型,隻要實作克隆自身的方法,就可以通過這個方法來擷取新的對象,沒必要再去通過new來建立。

原型模式的兩種表現形式

原型模式有兩種表現形式(僅僅是原型模式的不同實作):簡單形式和登記形式。

  簡單形式的原型模式​

Java設計模式—原型模式(prototype pattern)

這種模式涉及到三種角色分别如下:

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

public class Client {
    /**
     * 原型接口對象
     */
    private Prototype prototype;
    /**
     * 構造方法
     */
    public Client(Prototype prototype){
        this.prototype = prototype;
    }
    public void operation(Prototype example){
        Prototype copyPrototype = prototype.clone();


    }
}      

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

public interface Prototype{
    /**
     * 克隆自身的方法
     */
    public Object clone();
}      

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

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;
    }
}      

  登記形式的原型模式​

登記形式相比簡單形式多了一個原型管理器(PrototypeManager)角色,該角色的作用是建立具體原型類的對象,并記錄每一個被建立的對象。

Java設計模式—原型模式(prototype pattern)

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

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;
    }
}      

  兩種形式的差別​

當建立的原型對象數目較少而且比較固定的話,采取簡單形式。在這種情況下,原型對象的引用可以由用戶端自己儲存。

當建立的原型對象數目不固定的話,采取登記形式。在這種情況下,用戶端不儲存對原型對象的引用,這個任務被交給管理者對象。在複制一個原型對象之前,用戶端可以檢視管理者對象是否已經有一個滿足要求的原型對象。如果有,可以直接從管理者類取得這個對象引用;反之,用戶端就需要自行複制此原型對象。

淺拷貝與深拷貝

原型模式主要用于對象的複制,它的核心是就是類圖中的原型類Prototype。Prototype類需要具備以下兩個條件:

1)實作Cloneable接口。在java語言有一個Cloneable接口,它的作用隻有一個,就是在運作時通知虛拟機可以安全地在實作了此接口的類上使用clone方法。在java虛拟機中,隻有實作了這個接口的類才可以被拷貝,否則在運作時會抛出 CloneNotSupportedException異常。

2)重寫Object類中的clone方法。Java中,所有類的父類都是Object類,Object類中有一個clone方法,作用是傳回對象的一個拷貝,但是其作用域protected類型的,一般的類無法調用,是以,Prototype類需要将clone方法的作用域修改為public類型。

注:Object類的clone方法隻會拷貝java中的8中基本類型以及他們的封裝類型,另外還有String類型。對于數組、容器對象、引用對象等都不會拷貝,這就是淺拷貝。如果要實作深拷貝,必須将原型模式中的數組、容器對象、引用對象等另行拷貝。

  淺拷貝(shallow copy)​

被複制對象的所有變量都含有與原來的對象相同的值(僅對于簡單的值類型資料),而所有的對其他對象的引用都仍然指向原來的對象。換言之,隻負責克隆按值傳遞的資料(比如:基本資料類型、String類型)。

實體類,具體代碼如下:

package com.yoodb;


public class Prototype implements Cloneable{
  private String name;


  public String getName() {
    return name;
  }


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


  @Override
  protected Object clone() {
    // TODO Auto-generated method stub
    try {
      return super.clone();
    } catch (CloneNotSupportedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    return null;
  }




}      

測試函數,具體代碼如下:

package com.yoodb;


public class TestMain {
  public static void main(String[] args) {
    Prototype pro = new Prototype();
    pro.setName("歡迎收藏www.yoodb.com");
    Prototype prot = (Prototype) pro.clone();
    prot.setName("歡迎收藏www.yoodb.com");
    System.out.println("original object:" + pro.getName());
    System.out.println("cloned object:" + prot.getName());
  }


}      

運作結果如下:

original object:歡迎收藏www.yoodb.com
cloned object:歡迎收藏www.yoodb.com      

  深拷貝 (deep copy)​

被複制對象的所有的變量都含有與原來的對象相同的值,除去那些引用其他對象的變量。那些引用其他對象的變量将指向被複制過的新對象,而不再是原有的那些被引用的對象。換言之,除了淺度克隆要克隆的值外,還負責克隆引用類型的資料,基本上就是被克隆執行個體所有的屬性的資料都會被克隆出來。

執行個體一​

實體類,具體代碼如下:

package com.yoodb;


public class Prototype implements Cloneable{
  private String name;


  public String getName() {
    return name;
  }


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


  @Override
  protected Object clone() {
    // TODO Auto-generated method stub
    try {
      return super.clone();
    } catch (CloneNotSupportedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    return null;
  }




}      
//實體二
package com.yoodb;


public class NewPrototype implements Cloneable{
  private String id;


  private Prototype prototype;


  public String getId() {
    return id;
  }


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


  public Prototype getPrototype() {
    return prototype;
  }


  public void setPrototype(Prototype prototype) {
    this.prototype = prototype;
  }


  @Override
  protected Object clone() {
    NewPrototype prot = null;
    try {
      prot = (NewPrototype) super.clone();
      prot.prototype = (Prototype) this.getPrototype().clone();
      return prot;
    } catch (Exception e) {
      // TODO: handle exception
    }
    return null;
  }




}      

測試函數,具體代碼如下:

package com.yoodb;


public class TestMain {
  public static void main(String[] args) {
    //普通指派
    Prototype pro = new Prototype();
    pro.setName("歡迎收藏blog.yoodb.com");
    NewPrototype newPro = new NewPrototype();
    newPro.setId("yoodb");
    newPro.setPrototype(pro);
    //克隆指派
    NewPrototype proc = (NewPrototype) newPro.clone();
    proc.setId("yoodb");
    proc.getPrototype().setName("歡迎收藏blog.yoodb.com");


    System.out.println("original object id:" + newPro.getId());
    System.out.println("original object name:" + newPro.getPrototype().getName());


    System.out.println("cloned object id:" + proc.getId());
    System.out.println("cloned object name:" + proc.getPrototype().getName());
  }


}      

運作結果如下:

original object id:yoodb
original object name:歡迎收藏blog.yoodb.com
cloned object id:yoodb
cloned object name:歡迎收藏blog.yoodb.com      

執行個體二​

利用串行化來實作深克隆,把對象寫道流裡的過程是串行化(Serilization)過程;把對象從流中讀出來是并行化(Deserialization)過程。

實體類,具體代碼如下:

package com.yoodb;


import java.io.Serializable;


public class Prototype implements Serializable{


  private static final long serialVersionUID = 1L;


  private String name;


  public String getName() {
    return name;
  }


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


}      
//實體二
package com.yoodb;


import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;


public class NewPrototype implements Serializable{


  private static final long serialVersionUID = 1L;


  private String id;


  private Prototype prototype;


  public String getId() {
    return id;
  }


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


  public Prototype getPrototype() {
    return prototype;
  }


  public void setPrototype(Prototype prototype) {
    this.prototype = prototype;
  }


  protected Object deepClone(){
    try {
      ByteArrayOutputStream bo = new ByteArrayOutputStream();
      ObjectOutputStream oo = new ObjectOutputStream(bo);
      oo.writeObject(this);


      ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
      ObjectInputStream oi = new ObjectInputStream(bi);
      return oi.readObject();
    } catch (Exception e) {
      // TODO: handle exception
    }
    return null;
  }


}      

測試函數,具體代碼如下:

package com.yoodb;


public class TestMain {
  public static void main(String[] args) {
    //普通指派
    Prototype pro = new Prototype();
    pro.setName("歡迎收藏www.yoodb.com");
    NewPrototype newPro = new NewPrototype();
    newPro.setId("yoodb");
    newPro.setPrototype(pro);
    //克隆指派
    NewPrototype proc = (NewPrototype) newPro.deepClone();
    proc.setId("yoodb");
    proc.getPrototype().setName("歡迎收藏www.yoodb.com");


    System.out.println("original object id:" + newPro.getId());
    System.out.println("original object name:" + newPro.getPrototype().getName());


    System.out.println("cloned object id:" + proc.getId());
    System.out.println("cloned object name:" + proc.getPrototype().getName());
  }


}      

運作結果如下:

original object id:yoodb
original object name:歡迎收藏www.yoodb.com
cloned object id:yoodb
cloned object name:歡迎收藏www.yoodb.com      

  克隆滿足的條件​

clone()方法将對象複制了一份并返還給調用者。所謂“複制”的含義與clone()方法是怎麼實作的。一般而言,clone()方法滿足以下的描述:

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

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

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

原型模式使用場景

1)資源優化場景。

2)類初始化需要消化非常多的資源,這個資源包括資料、硬體資源等。

3)性能和安全要求的場景。

4)通過 new 産生一個對象需要非常繁瑣的資料準備或通路權限,則可以使用原型模式。

5)一個對象多個修改者的場景。

6)一個對象需要提供給其他對象通路,而且各個調用者可能都需要修改其值時,可以考慮使用原型模式拷貝多個對象供調用者使用。

7)在實際項目中,原型模式很少單獨出現,一般是和工廠方法模式一起出現,通過 clone 的方法建立一個對象,然後由工廠方法提供給調用者。

注:原型模式是通過拷貝一個現有對象生成新對象的。淺拷貝實作Cloneable,重寫,深拷貝是通過實作 Serializable 讀取二進制流。

原型模式優缺點

原型模式的優點是允許在運作期間,由客戶來注冊符合原型接口的實作類型,也可以動态地改變具體的實作類型,看起來接口沒有任何變化,然而運作的已經是另外一個類執行個體了。因為克隆一個原型就類似于執行個體化一個類。

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