建立對象_原型(Prototype)模式_深拷貝

 
舉例
    剛給個人客廳作了裝修,朋友也但願給他的客廳作裝修,他可能會把我家的裝修方案拿過來改改就成,個人裝修方案就是原型。
 
定義
    使用原型實例指定將要建立的對象類型,經過複製這個實例建立新的對象。
 
應用場景
    當建立一些很是耗時的大對象或者建立過程很是複雜時。
 
 
複製原型對象不必定是指從內存中進行復制,原型數據也可能保存在數據庫裏。
通常狀況下,OOP 語言都提供了內存中對象的複製能力,Java 語言提供了對象的淺拷貝。
 
 
淺拷貝(Shallow copy):複製一個對象時,若是它的一個屬性是引用,則複製這個引用,使之指向內存中同一個對象;
深拷貝(Deep copy):複製一個對象時,爲此屬性建立了一個新對象,讓其引用指向它。
 
郵遞快遞的場景:
顧客:「給我幾個快遞。」
快遞員:「寄往什麼地方?寄給...?」
顧客:「和上次差很少同樣,只是郵寄給另一個地址,這裏是郵寄地址...「把郵寄地址的紙條給快遞員。
快遞員:「好。」
    覺得保存了用戶之前的郵寄信息,只要複製這些數據,而後經過簡單的修改就能夠快速地建立新的快遞數據了。
 
注意:咱們在複製新的數據時,須要特別注意不能把全部數據都複製過來,如,當對象包含主鍵時,不能使用原型數據的主鍵,必須建立一個新的主鍵。
 
 
Java 的 java.lang.Object 方法裏就提供了克隆方法 clone( ),原則上彷佛全部類都擁有此功能,可是它的使用有以下限制:
  1. 要實現克隆,必須實現 java.lang.Cloneable 接口,不然在運行時調用 clone( ) 方法,會拋出 CloneNotSupportedException異常。
  2. 返回的是 Object類型的對象,因此使用時可能須要強制類型轉換。
  3. 該方法是 protected的,若是想讓外部對象使用它,必須在子類重寫該方法,設定其訪問範圍是 public的,參見 PackageInfo 的 clone( ) 方法。
  4. Object 的 clone( ) 方法的複製是採用逐字節的方式從內存賦值數據,複製了屬性了引用,而屬性所指向的對象自己沒有被複制,所以所複製的引用指向了相同的對象。即 淺拷貝。
 
public   class  PackageInfo  implements  Cloneable {
     public   PackageInfo  clone() {
         try  {
             return  (PackageInfo)  super .clone();
        }  catch  (CloneNotSupportedException e) {
            System. out .println( "Cloning not allowed." );
             return   null ;
        }
    }

     // 靜態工廠方法:根據原型建立一份副本
     public   static  PackageInfo clonePackage(String userName) {
         // 根據 userName加載一條用戶之前的數據做爲原型數據(數據庫或其它保存的數據)
        PackageInfo prototype =  loadPackageInfo (userName);
         // 再在內存中克隆這條數據
        prototype =  prototypr .clone();
         // 初始化數據 id(主鍵)
        prototype. setId ( null );
         // 返回數據
         return  prototype;
    }
}
 
    在實際應用中,使用原型模式建立對象圖(Object Graph)很是便捷。
對象圖不是一個單個對象,而是一組聚合的對象,改組對象有一個根對象。
 
 
 
 
深拷貝(Deep copy)的兩種實現方式:
  1. 複製對象時,遞歸地調用屬性對象的克隆方法。根據具體的類,撰寫出實現特定類型的深拷貝方法。
        通常咱們很難實現一個通常性的方法來完成任何類型對象的深拷貝。根據反射獲得屬性的類型,而後依照它的類型構造對象,但前提是:這些屬性的類型必須含有一個公有的默認構造方法,不然做爲一個通常性的方法,很難肯定傳遞給非默認構造方法的參數值;此外,若是屬性類型是接口或者抽象類型,必須提供查找到相關的具體類方法,做爲一個通常性的方法,這個也很難辦到。
     
  2. 若是類實現了 java.io.Serializable 接口,把原型對象序列化,而後反序列化後獲得得對象,其實就是一個新的深拷貝對象。
 
//DeepCopyBean實現了 java.io.Serializable接口
public   class   DeepCopyBean   implements  Serializable {
     // 原始類型屬性
     private   int   primitiveField ;
     // 對象屬性
     private  String  objectField ;
     // 首先序列化本身到流中,而後從流中反序列化,獲得得對象即是一個新的深拷貝
     public  DeepCopyBean deepCopy() {
         try  {
            ByteArrayOutputStream buf =  new  ByteArrayOutputStream();
            ObjectOutputStream o =  new  ObjectOutputStream(buf);
            o.writeObject( this );
            ObjectInputStream in =  new  ObjectInputStream(
                     new  ByteArrayInputStream(buf.toByteArray()));
             return  (DeepCopyBean) in.readObject();
        }  catch  (Exception e) {
            e.printStackTrace();
        }
         return   null ;
    }
    // 屬性 get、set 方法略...
 
 
    // 測試demo
     public   static   void  main(String[] args) {
        DeepCopyBean originalBean =  new  DeepCopyBean();
         // 建立兩個 String對象,其中一個在 JVM的字符串池(String pool)裏,屬性引用指向另一個在堆裏的對象
        originalBean.setObjectField( new  String( "guilin" ));
        originalBean.setPrimitiveField(50);
         // 深拷貝
        DeepCopyBean newBean = originalBean.deepCopy();
         // 原始類型屬性值比較:true
        System. out .println( "primitiveField ==:"
                + (originalBean.getPrimitiveField() == newBean
                        .getPrimitiveField()));
         // 對象屬性值比較:false(證實未指向相同的地址)
        System. out .println( "objectField ==:"
                + (originalBean.getObjectField() == newBean.getObjectField()));
         // 對象屬性 equals 比較:true
        System. out .println( "objectField equal:"
                + (originalBean.getObjectField().equals(newBean
                        .getObjectField())));
    }
}

 

使用這種方式進行深拷貝注意:
  1. 它只能複製實現 Serializable接口類型的對象,其屬性也是可序列化的;
  2. 序列化和反序列化比較耗時。
 
 
總結:使用原型模式有如下優勢:
  • 建立大的聚合對象圖時,沒有必要爲每一個層次的子對象建立相應層次的工廠類。
  • 方便實例化,只要複製對象,而後初始化對象,就能夠獲得想要的對象。
相關文章
相關標籤/搜索