1.原型模式概述
在使用原型模式時,我們需要首先創建一個原型對象,再通過複製這個原型對象來創建更多同類型的對象。
原型模式的工作原理:將一個原型對象傳給那個要發動創建的對象,這個要發動創建的對象通過請求原型對象拷貝自己來實現創建過程。由於軟件系統中我們經常會遇到需要創建多個相同或者相似對象的情況,因此原型模式在真實開發中的使用頻率還是非常高的。原型模式是一種“另類”的創建型模式,創建克隆對象的工廠就是原型類自身,工廠方法由克隆方法來實現。需要注意的是通過克隆方法所創建的對象是全新的對象,它們在內存中擁有新的地址,通常對克隆所產生的對象進行修改對原型對象不會造成任何影響,每一個克隆對象都是相互獨立的。通過不同的方式修改可以得到一系列相似但不完全相同的對象。
2.原型模式結構
在原型模式結構圖中包含如下幾個角色:
抽象原型類(Prototype):它是聲明克隆方法的接口,是所有具體原型類的公共父類,可以是抽象類也可以是接口,甚至可以是具體實現類。
具體原型類(ConcretePrototype):它實現在抽象原型類中聲明的克隆方法,在克隆方法中返回自己的一個克隆對象。
客戶類(Client):讓一個原型對象克隆自身從而創建一個新的對象,在客戶類中只需要直接實例化或通過工廠方法等方式創建一個原型對象,再通過調用該對象的克隆方法即可得到多個相同的對象。由於客戶類針對抽象原型類Prototype編程,因此用戶可以根據需要選擇具體原型類,系統具有較好的可擴展性,增加或更換具體原型類都很方便。
通用實現方法
通用的克隆實現方法是在具體原型類的克隆方法中實例化一個與自身類型相同的對象並將其返回,並將相關的參數傳入新創建的對象中,保證它們的成員屬性相同。示意代碼如下:
class ConcretePrototype implements Prototype {
private String attr;
public void setAttr(String attr){
this.attr = attr;
}
public String getAttr(){
return this.attr;
}
public Prototype clone(){
Prototype prototype = new ConcretePrototype();
prototype.setAttr(attr);
return prototype;
}
}
Java語言提供的clone()方法
學過Java語言的人都知道,所有的Java類都繼承自java.lang.Object。事實上,Object類提供一個clone()方法,可以將一個Java對象複製一份。因此在Java中可以直接使用Object提供的clone()方法來實現對象的克隆,Java語言中的原型模式實現很簡單。
需要注意的是能夠實現克隆的Java類必須實現一個標識接口Cloneable,表示這個Java類支持被複制。如果一個類沒有實現這個接口但是調用了clone()方法,Java編譯器將拋出異常。如下代碼所示:
class ConcretePrototype implements Cloneable {
......
public Prototype clone(){
Object object = null;
try {
object = super.clone();
} catch(ClassNotSupportedException exception){
System.err.println("Not support cloneable");
}
return (Prototype)object;
}
......
}
在客戶端創建原型對象和克隆對象也很簡單,如下代碼所示:
Prototype obj1 = new ConcretePrototype();
Prototype obj2 = obj1.clone();
3.原型模式實例
Sunny公司開發人員決定使用原型模式來實現工作週報的快速創建,快速創建工作週報結構圖如下所示:
工程目錄結構
實現代碼
具體原型類
package cn.red.prototype;
public class WeekLog implements Cloneable {
private String name;
private String date;
private String content;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public WeekLog clone(){
Object obj = null;
try {
obj = super.clone();
return (WeekLog) obj;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
客戶端測試代碼:
package cn.red.prototype;
public class Client {
public static void main(String[] args) {
WeekLog pre_log = new WeekLog();
pre_log.setName("Tom");
pre_log.setDate("第1周");
pre_log.setContent("這種工作很忙,每天加班!");
System.out.println("****週報****");
System.out.println("姓名:" + pre_log.getName());
System.out.println("周次:" + pre_log.getDate());
System.out.println("內容:" + pre_log.getContent());
System.out.println("------------------------");
WeekLog new_log;
new_log = pre_log.clone();
new_log.setDate("第2周");
System.out.println("****週報****");
System.out.println("姓名:" + new_log.getName());
System.out.println("周次:" + new_log.getDate());
System.out.println("內容:" + new_log.getContent());
}
}
輸出結果:
姓名:Tom
周 次:第1周
內容:這種工作很忙,每天加班!
------------------------
****週報****
姓名:Tom
周次:第2周
內容:這種工作很忙,每天加班!
3.兩種不同的克隆方法
在Java語言中,數據類型分爲值類型(基本數據類型)和引用類型,值類型包括int、double、byte、boolean、char等簡單數據類型,引用類型包括類、接口、數組等複雜類型。淺克隆和深克隆的主要區別在於是否支持引用類型的成員變量的複製。
1.淺克隆
在淺克隆中,如果原型對象的成員變量是值類型,將複製一份給克隆對象;如果原型對象的成員變量是引用類型,則將引用對象的地址複製一份給克隆對象,也就是說原型對象和克隆對象的成員變量指向相同的內存地址。簡言之,在淺克隆中,當對象被複制時只複製它本身和其中包含的值類型的成員變量,而引用類型的成員對象並沒有複製。
淺克隆示意圖:
在Java語言中,通過覆蓋Object類的clone()方法可以實現淺克隆。爲了讓大家更好地理解淺克隆和深克隆的區別,我們首先使用淺克隆來實現工作週報和附件類的複製,其結構如下圖所示:
工程目錄結構:
附件類:
package cn.red.prototype;
public class Attachment {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void download() {
System.out.println("下載附件,文件名爲:" + name);
}
}
工作週報類:
package cn.red.prototype;
public class WeekLog implements Cloneable {
private Attachment attachment;
private String name;
private String date;
private String content;
public Attachment getAttachment() {
return attachment;
}
public void setAttachment(Attachment attachment) {
this.attachment = attachment;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
//使用clone()方法實現淺克隆複製
public WeekLog clone() {
Object obj = null;
try {
obj = super.clone();
return (WeekLog) obj;
} catch (CloneNotSupportedException e) {
e.printStackTrace();
return null;
}
}
}
客戶端代碼如下所示:
package cn.red.prototype;
public class Client {
public static void main(String[] args) {
WeekLog log_previous,log_new;
//創建原型對象
log_previous = new WeekLog();
//創建附件對象
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.深克隆
在深克隆中,無論原型對象的成員變量是值類型還是引用類型,都將複製一份給克隆對象,深克隆將原型對象的所有引用對象也複製一份給克隆對象。簡言之,在深克隆中,除了對象本身被複制外,對象所包含的所有成員變量也將複製。
在Java語言中,如果需要實現深克隆,可以通過序列化(Serialization)等方式來實現。序列化就是將對象寫到流的過程,寫到流中的對象是原有對象的一個拷貝,而原對象仍然存在於內存中。通過序列化實現的拷貝不僅可以複製對象本身,而且可以複製其引用的成員對象,因此通過序列化將對象寫到一個流中,再從流中將其讀出來,可以實現深克隆。需要注意的是能夠實現序列化的對象其類必須實現Serializable接口,否則無法實現序列化操作。下面我們使用深克隆技術來實現工作週報和附件對象的複製,由於要將附件對象和工作週報對象都寫入流中,因此兩個類均需要實現Serializable接口,其結構如圖所示:
附件類:
package cn.red.prototype;
import java.io.Serializable;
public class Attachment implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void download() {
System.out.println("下載附件,文件名爲:" + name);
}
}
工作週報類:
package cn.red.prototype;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class WeekLog implements Serializable {
private static final long serialVersionUID = 1L;
private Attachment attachment;
private String name;
private String date;
private String content;
public Attachment getAttachment() {
return attachment;
}
public void setAttachment(Attachment attachment) {
this.attachment = attachment;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
//使用deepClone()方法實現深克隆複製
public WeekLog deepClone() throws IOException, ClassNotFoundException {
//將對象寫入流中
ByteArrayOutputStream bao = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bao);
oos.writeObject(this);
//將對象從流中取出
ByteArrayInputStream bis = new ByteArrayInputStream(bao.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (WeekLog) ois.readObject();
}
}
客戶端代碼如下所示:
package cn.red.prototype;
public class Client {
public static void main(String[] args) {
WeekLog log_previous,log_new = null;
//創建原型對象
log_previous = new WeekLog();
//創建附件對象
Attachment attachment = new Attachment();
//將附件添加到週報中
log_previous.setAttachment(attachment);
//調用克隆方法克隆對象
try {
log_new = log_previous.deepClone();
} catch (Exception e) {
e.printStackTrace();
}
//比較週報
System.out.println("週報是否相同?" + (log_previous==log_new));
//比較附件
System.out.println("附件是否相同?" + (log_previous.getAttachment()==log_new.getAttachment()));
}
}
輸出結果:
週報是否相同?false
附件是否相同?false
從輸出結果可以看出,由於使用了深克隆技術,附件對象也得以複製,因此用”==”比較原型對象的附件和克隆對象的附件時輸出結果均爲false。深克隆技術實現了原型對象和克隆對象的完全獨立,對任意克隆對象的修改都不會給其他對象產生影響,是一種更爲理想的克隆實現方式。