物件的克隆——原型模式(三):淺克隆,深克隆
7.4 帶附件的週報
通過引入原型模式,Sunny軟體公司OA系統支援工作週報的快速克隆,極大提高了工作週報的編寫效率,受到員工的一致好評。但有員工又發現一個問題,有些工作週報帶有附件,例如經理助理“小龍女”的週報通常附有本週專案進展報告彙總表、本週客戶反饋資訊彙總表等,如果使用上述原型模式來複制週報,週報雖然可以複製,但是週報的附件並不能複製,這是由於什麼原因導致的呢?如何才能實現週報和附件的同時複製呢?我們在本節將討論如何解決這些問題。
在回答這些問題之前,先介紹一下兩種不同的克隆方法,淺克隆(ShallowClone)和深克隆(DeepClone)。在Java語言中,資料型別分為值型別(基本資料型別)和引用型別,值型別包括int、double、byte、boolean、char等簡單資料型別,引用型別包括類、介面、陣列等複雜型別
1.淺克隆
在淺克隆中,如果原型物件的成員變數是值型別,將複製一份給克隆物件;如果原型物件的成員變數是引用型別,則將引用物件的地址複製一份給克隆物件,也就是說原型物件和克隆物件的成員變數指向相同的記憶體地址。簡單來說,在淺克隆中,當物件被複制時只複製它本身和其中包含的值型別的成員變數,而引用型別的成員物件並沒有複製,如圖7-4所示:
在Java語言中,通過覆蓋Object類的clone()方法可以實現淺克隆。為了讓大家更好地理解淺克隆和深克隆的區別,我們首先使用淺克隆來實現工作週報和附件類的複製
附件類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。深克隆技術實現了原型物件和克隆物件的完全獨立,對任意克隆物件的修改都不會給其他物件產生影響,是一種更為理想的克隆實現方式。