1. 程式人生 > >物件的克隆——原型模式(三):淺克隆,深克隆

物件的克隆——原型模式(三):淺克隆,深克隆

7.4 帶附件的週報

通過引入原型模式,Sunny軟體公司OA系統支援工作週報的快速克隆,極大提高了工作週報的編寫效率,受到員工的一致好評。但有員工又發現一個問題,有些工作週報帶有附件,例如經理助理“小龍女”的週報通常附有本週專案進展報告彙總表、本週客戶反饋資訊彙總表等,如果使用上述原型模式來複制週報,週報雖然可以複製,但是週報的附件並不能複製,這是由於什麼原因導致的呢?如何才能實現週報和附件的同時複製呢?我們在本節將討論如何解決這些問題。

在回答這些問題之前,先介紹一下兩種不同的克隆方法,淺克隆(ShallowClone)和深克隆(DeepClone)。在Java語言中,資料型別分為值型別(基本資料型別)和引用型別,值型別包括int、double、byte、boolean、char等簡單資料型別,引用型別包括類、介面、陣列等複雜型別

淺克隆和深克隆的主要區別在於是否支援引用型別的成員變數的複製,下面將對兩者進行詳細介紹。

1.淺克隆

在淺克隆中,如果原型物件的成員變數值型別,將複製一份給克隆物件;如果原型物件的成員變數引用型別,則將引用物件的地址複製一份給克隆物件,也就是說原型物件和克隆物件的成員變數指向相同的記憶體地址。簡單來說,在淺克隆中,當物件被複制時只複製它本身和其中包含的值型別的成員變數,而引用型別的成員物件並沒有複製,如圖7-4所示:
這裡寫圖片描述

在Java語言中,通過覆蓋Object類的clone()方法可以實現淺克隆。為了讓大家更好地理解淺克隆和深克隆的區別,我們首先使用淺克隆來實現工作週報和附件類的複製

,其結構如圖7-5所示:
這裡寫圖片描述

附件類Attachment程式碼如下:

/附件類
class Attachment
{
       private  String name; //附件名
       public  void setName(String name)
       {
              this.name  = name;
       }
       public  String getName()
       {
              return  this.name;
       }
     public void download()
     {
            System.out
.println("下載附件,檔名為" + name); } }

修改工作週報類WeeklyLog,修改後的程式碼如下:

//工作週報WeeklyLog
class WeeklyLog implements Cloneable
{
     //為了簡化設計和實現,假設一份工作週報中只有一個附件物件,實際情況中可以包含多個附件,可以通過List等集合物件來實現
       private Attachment attachment;
private String name;
       private  String date;
       private  String content;
    public void setAttachment(Attachment  attachment) {
              this.attachment = attachment;
       }
       public  void setName(String name) {
              this.name  = name;
       }
       public  void setDate(String date) {
              this.date  = date;
       }
       public  void setContent(String content) {
              this.content  = content;
       }
public Attachment  getAttachment(){
              return (this.attachment);
       }
       public  String getName() {
              return  (this.name);
       }
       public  String getDate() {
              return  (this.date);
       }
       public  String getContent() {
              return  (this.content);
       }
     //使用clone()方法實現淺克隆
       public WeeklyLog clone()
       {
              Object obj = null;
              try
              {
                     obj = super.clone();
                     return (WeeklyLog)obj;
              }
              catch(CloneNotSupportedException  e)
              {
                     System.out.println("不支援複製!");
                     return null;
              }
       }
}

客戶端程式碼如下所示:

class Client
{
       public  static void main(String args[])
       {
              WeeklyLog  log_previous, log_new;
              log_previous  = new WeeklyLog(); //建立原型物件
              Attachment  attachment = new Attachment(); //建立附件物件
              log_previous.setAttachment(attachment);  //將附件新增到週報中
              log_new  = log_previous.clone(); //呼叫克隆方法建立克隆物件
              //比較週報
              System.out.println("週報是否相同? " + (log_previous ==  log_new));
              //比較附件
              System.out.println("附件是否相同? " +  (log_previous.getAttachment() == log_new.getAttachment()));
       }
}

編譯並執行程式,輸出結果如下:

週報是否相同?  false
附件是否相同? true

由於使用的是淺克隆技術,因此工作週報物件複製成功,通過“==”比較原型物件和克隆物件的記憶體地址時輸出false;但是比較附件物件的記憶體地址時輸出true,說明它們在記憶體中是同一個物件。

2.深克隆

在深克隆中,無論原型物件的成員變數是值型別還是引用型別,都將複製一份給克隆物件,深克隆將原型物件的所有引用物件也複製一份給克隆物件。簡單來說,在深克隆中,除了物件本身被複制外,物件所包含的所有成員變數也將複製,如圖7-6所示:
這裡寫圖片描述

在Java語言中,如果需要實現深克隆,可以通過序列化(Serialization)等方式來實現。序列化就是將物件寫到流的過程,寫到流中的物件是原有物件的一個拷貝,而原物件仍然存在於記憶體中。通過序列化實現的拷貝不僅可以複製物件本身,而且可以複製其引用的成員物件,因此通過序列化將物件寫到一個流中,再從流裡將其讀出來,可以實現深克隆。需要注意的是能夠實現序列化的物件其類必須實現Serializable介面,否則無法實現序列化操作。下面我們使用深克隆技術來實現工作週報和附件物件的複製,由於要將附件物件和工作週報物件都寫入流中,因此兩個類均需要實現Serializable介面,其結構如圖7-7所示:
這裡寫圖片描述

修改後的附件類Attachment程式碼如下:

import  java.io.*;
//附件類
class  Attachment implements Serializable
{
       private  String name; //附件名
       public  void setName(String name)
       {
              this.name  = name;
       }
       public  String getName()
       {
              return  this.name;
       }
     public void download()
     {
            System.out.println("下載附件,檔名為" + name);
     }
}

工作週報類WeeklyLog不再使用Java自帶的克隆機制,而是通過序列化來從頭實現物件的深克隆,我們需要重新編寫clone()方法,修改後的程式碼如下:

import  java.io.*;
//工作週報類
class  WeeklyLog implements Serializable
{
       private  Attachment attachment;
       private  String name;
       private  String date;
       private  String content;
       public  void setAttachment(Attachment attachment) {
              this.attachment  = attachment;
       }
       public  void setName(String name) {
              this.name  = name;
       }
       public  void setDate(String date) {
              this.date  = date;
       }
       public  void setContent(String content) {
              this.content  = content;
       }
       public  Attachment getAttachment(){
              return  (this.attachment);
       }
       public  String getName() {
              return  (this.name);
       }
       public  String getDate() {
              return  (this.date);
       }
       public  String getContent() {
              return  (this.content);
       }
   //使用序列化技術實現深克隆
       public WeeklyLog deepClone() throws  IOException, ClassNotFoundException, OptionalDataException
       {
              //將物件寫入流中
              ByteArrayOutputStream bao=new  ByteArrayOutputStream();
              ObjectOutputStream oos=new  ObjectOutputStream(bao);
              oos.writeObject(this);

              //將物件從流中取出
              ByteArrayInputStream bis=new  ByteArrayInputStream(bao.toByteArray());
              ObjectInputStream ois=new  ObjectInputStream(bis);
              return  (WeeklyLog)ois.readObject();
       }
}

客戶端程式碼如下所示:

class Client
{
       public  static void main(String args[])
       {
              WeeklyLog  log_previous, log_new = null;
              log_previous  = new WeeklyLog(); //建立原型物件
              Attachment  attachment = new Attachment(); //建立附件物件
              log_previous.setAttachment(attachment);  //將附件新增到週報中
              try
              {
                     log_new =  log_previous.deepClone(); //呼叫深克隆方法建立克隆物件                  
              }
              catch(Exception e)
              {
                     System.err.println("克隆失敗!");
              }
              //比較週報
              System.out.println("週報是否相同? " + (log_previous ==  log_new));
              //比較附件
              System.out.println("附件是否相同? " +  (log_previous.getAttachment() == log_new.getAttachment()));
       }
}

編譯並執行程式,輸出結果如下:

週報是否相同?  false
附件是否相同?  false

從輸出結果可以看出,由於使用了深克隆技術,附件物件也得以複製,因此用“==”比較原型物件的附件和克隆物件的附件時輸出結果均為false。深克隆技術實現了原型物件和克隆物件的完全獨立,對任意克隆物件的修改都不會給其他物件產生影響,是一種更為理想的克隆實現方式。