此系列文章為清華大學(xué)出版社出版劉偉編著《Java設(shè)計(jì)模式》的學(xué)習(xí)筆記卢鹦。
1 概述
原型模式是一種對(duì)象創(chuàng)建型模式,它的工作原理很簡(jiǎn)單:將一個(gè)原型對(duì)象傳給要發(fā)動(dòng)創(chuàng)建的對(duì)象(即客戶(hù)端對(duì)象)捐寥,這個(gè)發(fā)動(dòng)創(chuàng)建的對(duì)象通過(guò)請(qǐng)求原型對(duì)象復(fù)制自己來(lái)實(shí)現(xiàn)創(chuàng)建過(guò)程。
2 結(jié)構(gòu)與實(shí)現(xiàn)
2.1 原型模式結(jié)構(gòu)
原型模式包含 3 個(gè)角色:
- AbstractPrototype (抽象原型類(lèi)):它是聲明克隆方法的接口,是所有具體原型類(lèi)的公共父類(lèi),它可以是抽象類(lèi)也可以是接口咧虎,甚至還可以是具體實(shí)現(xiàn)類(lèi)。
- ConcretePrototype (具體圓形類(lèi)):它實(shí)現(xiàn)在抽象原型類(lèi)中聲明的克隆方法计呈,在克隆方法中返回一個(gè)自己的克隆對(duì)象砰诵。
- Client (客戶(hù)類(lèi)):在客戶(hù)類(lèi)中,讓一個(gè)原型對(duì)象克隆自身從而創(chuàng)建一個(gè)新的對(duì)象捌显,只需要直接實(shí)例化或通過(guò)工廠(chǎng)方法等方式創(chuàng)建一個(gè)原型對(duì)象茁彭,再通過(guò)調(diào)用該對(duì)象的克隆方法即可得到多個(gè)相同的對(duì)象。由于客戶(hù)類(lèi)針對(duì)抽象原型類(lèi)編程扶歪,因此用戶(hù)可以根據(jù)需要選擇具體的原型類(lèi)理肺,系統(tǒng)具有較好的可擴(kuò)展性,增加或更換具體的原型類(lèi)都很方便善镰。
2.2 淺克隆與深克隆
根據(jù)在復(fù)制原型對(duì)象的同時(shí)妹萨,是否復(fù)制包含在原型對(duì)象中的引用對(duì)象成員,原型模式的克隆機(jī)制分為淺克隆和深克隆炫欺。
2.2.1 淺克隆
淺克隆與深克隆的討論針對(duì)的是原型類(lèi)中的引用對(duì)象成員乎完,而淺克隆的特點(diǎn)是不會(huì)在內(nèi)存中新建一個(gè)引用對(duì)象成員,與原型類(lèi)共用該引用對(duì)象成員品洛,在原型類(lèi)執(zhí)行淺克隆時(shí)囱怕,向克隆產(chǎn)生的新對(duì)象傳遞的是原型對(duì)象的引用對(duì)象的地址,造成兩個(gè)類(lèi)共享該引用成員變量毫别。
2.2.2 深克隆
與淺克隆相反娃弓,深克隆在內(nèi)存中重新申請(qǐng)空間,克隆一個(gè)不和原型類(lèi)共享的引用成員變量岛宦。
2.2.3 Java 中的 clone() 方法和 Cloneable 接口
Java 中台丛,java.lang.Object 類(lèi)提供了一個(gè) clone() 方法,可以將一個(gè)Java對(duì)象復(fù)制一份砾肺。因此在 Java 中可以直接使用該 clone() 方法來(lái)實(shí)現(xiàn)對(duì)象的淺克隆挽霉。
需要注意的是,能夠?qū)崿F(xiàn)克隆的Java類(lèi)必須實(shí)現(xiàn)一個(gè)標(biāo)識(shí)接口 Cloneable变汪,表示這個(gè) Java 類(lèi)支持被復(fù)制侠坎。如果一個(gè)類(lèi)沒(méi)有實(shí)現(xiàn)這個(gè)接口但是調(diào)用了 clone() 方法,Java 編譯器將會(huì)拋出一個(gè) CloneNotSupportedException 異常裙盾。
Java 中的 clone() 方法滿(mǎn)足以下幾點(diǎn):
- 對(duì)任何對(duì)象 x实胸,都有 x.clone() != x 他嫡,即克隆對(duì)象與原型對(duì)象不是同一個(gè)對(duì)象。
- 對(duì)任何對(duì)象 x庐完,都有 x.clone().getClass() == x.getClass() 钢属,即克隆對(duì)象與原型對(duì)象的類(lèi)型一樣。
- 如果對(duì)象 x 的 equals() 方法定義恰當(dāng)门躯,那么 x.clone().equals(x) 應(yīng)該成立淆党。
利用 Object 類(lèi)的 clone() 方法,需要以下幾個(gè)步驟:
- 在子類(lèi)中覆蓋父類(lèi)的 clone() 方法讶凉,并聲明為 public染乌。
- 在子類(lèi)的 clone() 方法中調(diào)用 super.clone()。
- 子類(lèi)需要實(shí)現(xiàn) Cloneable 接口懂讯。
此時(shí)荷憋,Object 類(lèi)相當(dāng)于抽象原型類(lèi),所有實(shí)現(xiàn)了 Cloneable 接口的類(lèi)相當(dāng)于原型類(lèi)域醇。
2.2.4 Java 中的 Serializable 接口
實(shí)現(xiàn)深克隆的方式有很多,當(dāng)我們利用字節(jié)流來(lái)實(shí)現(xiàn)深克隆時(shí)蓉媳,原型類(lèi)需要實(shí)現(xiàn) Serializable 接口譬挚。
2.3 原型模式舉例
一、背景介紹
在使用某 OA 系統(tǒng)時(shí)酪呻,有些崗位的員工發(fā)現(xiàn)他們每周的工作都大同小異减宣,因此在填寫(xiě)工作周報(bào)時(shí)很多內(nèi)容都是重復(fù)的,為了提高工作周報(bào)的創(chuàng)建效率玩荠,大家迫切希望有一種機(jī)制能夠快速創(chuàng)建相同或者相似的周報(bào)漆腌,包括創(chuàng)建周報(bào)的附件。請(qǐng)使用原型模式實(shí)現(xiàn)阶冈。
二闷尿、項(xiàng)目結(jié)構(gòu)
三、抽象原型類(lèi)
package CreationalPattern.PrototypePattern.AbstractPrototype;
import CreationalPattern.PrototypePattern.Attachment;
import java.io.IOException;
import java.io.Serializable;
public abstract class WeeklyLog implements Cloneable, Serializable {
protected Attachment attachment;
protected String name;
protected String date;
protected String content;
public abstract Attachment getAttachment();
public abstract void setAttachment(Attachment attachment);
public abstract String getName();
public abstract void setName(String name);
public abstract String getDate();
public abstract void setDate(String date);
public abstract String getContent();
public abstract void setContent(String content);
//todo:shadowClone()方法實(shí)現(xiàn)淺拷貝
public abstract WeeklyLog shadowClone();
//todo:deepClone()方法實(shí)現(xiàn)深拷貝
public abstract WeeklyLog deepClone() throws IOException, ClassNotFoundException;
}
四女坑、具體原型類(lèi)
package CreationalPattern.PrototypePattern.ConcretePrototype;
import CreationalPattern.PrototypePattern.AbstractPrototype.WeeklyLog;
import CreationalPattern.PrototypePattern.Attachment;
import java.io.*;
public class MyWeeklyLog extends WeeklyLog {
protected Attachment attachment;
protected String name;
protected String date;
protected 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;
}
//todo:shadowClone()方法實(shí)現(xiàn)淺拷貝
public CreationalPattern.PrototypePattern.AbstractPrototype.WeeklyLog shadowClone() {
Object obj = null;
try{
obj = super.clone();
return (CreationalPattern.PrototypePattern.AbstractPrototype.WeeklyLog)obj;
} catch (CloneNotSupportedException e) {
System.out.println("不支持復(fù)制");
return null;
}
}
//todo:deepClone()方法實(shí)現(xiàn)深拷貝
public WeeklyLog deepClone() throws IOException, ClassNotFoundException {
//todo:將對(duì)象寫(xiě)入流中
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
//todo:將對(duì)象從流中讀出
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
return (WeeklyLog)ois.readObject();
}
}
五填具、附件類(lèi)
package CreationalPattern.PrototypePattern;
import java.io.Serializable;
public class Attachment implements Serializable {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void download(){
System.out.println("下載附件 [ "+name+" ] ");
}
}
六、客戶(hù)類(lèi)
package CreationalPattern.PrototypePattern;
import CreationalPattern.PrototypePattern.AbstractPrototype.WeeklyLog;
import CreationalPattern.PrototypePattern.ConcretePrototype.MyWeeklyLog;
import java.io.IOException;
public class Client {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//todo:淺拷貝
WeeklyLog preLog1,newLog1;
preLog1 = new MyWeeklyLog();
Attachment attachment1 = new Attachment();
preLog1.setAttachment(attachment1);
newLog1 = preLog1.shadowClone();
System.out.println("WeeklyLog是否相同匆骗?"+(preLog1 == newLog1));//false
System.out.println("Attachment是否相同劳景?"+(preLog1.getAttachment() == newLog1.getAttachment()));//true
//todo:深拷貝
WeeklyLog preLog2,newLog2;
preLog2 = new MyWeeklyLog();
Attachment attachment2 = new Attachment();
preLog2.setAttachment(attachment2);
newLog2 = preLog2.deepClone();
System.out.println("WeeklyLog是否相同?"+(preLog2 == newLog2));//false
System.out.println("Attachment是否相同碉就?"+(preLog2.getAttachment()==newLog2.getAttachment()));//false
}
}
七盟广、測(cè)試結(jié)果
3 總結(jié)
3.1 原型模式優(yōu)點(diǎn)
- 當(dāng)創(chuàng)建新的對(duì)象實(shí)例較為復(fù)雜時(shí),使用原型模式可以簡(jiǎn)化對(duì)象的創(chuàng)建過(guò)程瓮钥,通過(guò)復(fù)制一個(gè)已有實(shí)例可以提高新勢(shì)力的創(chuàng)建效率筋量。
- 擴(kuò)展性較好烹吵,由于在原型模式中提高了創(chuàng)建原型類(lèi),在客戶(hù)端可以針對(duì)抽象原型類(lèi)進(jìn)行編程毛甲,而將具體原型類(lèi)寫(xiě)在配置文件中年叮,增加或減少產(chǎn)品類(lèi)對(duì)原有系統(tǒng)沒(méi)有任何影響。
- 原型模式提供了簡(jiǎn)化的創(chuàng)建結(jié)構(gòu)玻募,工廠(chǎng)方法模式常常需要有一個(gè)與產(chǎn)品類(lèi)等級(jí)結(jié)構(gòu)相同的工廠(chǎng)等級(jí)結(jié)構(gòu)只损,而原型模式不需要,原型模式中產(chǎn)品的復(fù)制是通過(guò)封裝在原型內(nèi)部的克隆方法實(shí)現(xiàn)的七咧,無(wú)需專(zhuān)門(mén)的工廠(chǎng)類(lèi)跃惫。
- 可以使用深克隆的方式保存對(duì)象的狀態(tài),使用原型模式將對(duì)象復(fù)制一份并將其狀態(tài)保存起來(lái)艾栋,以便在需要的時(shí)候使用(例如恢復(fù)到某一歷史狀態(tài))爆存,可輔助實(shí)現(xiàn)撤銷(xiāo)操作。
3.2 原型模式缺點(diǎn)
- 需要為每一個(gè)類(lèi)配備一個(gè)克隆方法蝗砾,而且該克隆方法位于一個(gè)類(lèi)的內(nèi)部先较,當(dāng)對(duì)已有的類(lèi)進(jìn)行改造時(shí)需要修改源代碼,違背了開(kāi)閉原則悼粮。
- 在實(shí)現(xiàn)深克隆時(shí)闲勺,需要編寫(xiě)較為復(fù)雜的代碼,而且當(dāng)對(duì)象之間存在多重的嵌套引用時(shí)扣猫,為了實(shí)現(xiàn)深克隆菜循,每一次對(duì)象對(duì)應(yīng)的類(lèi)都必須支持深克隆,實(shí)現(xiàn)起來(lái)可能會(huì)比較麻煩申尤。
3.3 原型模式適用環(huán)境
- 創(chuàng)建對(duì)象成本較大(例如初始化需要占用較長(zhǎng)的時(shí)間癌幕、占用太多的CPU資源或者網(wǎng)絡(luò)資源),新對(duì)象可以通過(guò)復(fù)制已有對(duì)象來(lái)獲得昧穿,如果是相似對(duì)象勺远,則可以對(duì)其成員變量稍作修改。
- 系統(tǒng)要保存對(duì)象的狀態(tài)时鸵,而對(duì)象的狀態(tài)變化很小谚中。
- 需要避免使用分層次的工廠(chǎng)類(lèi)來(lái)創(chuàng)建分層次的對(duì)象,并且類(lèi)的實(shí)例對(duì)象只有一個(gè)或很少幾個(gè)組合狀態(tài)寥枝,通過(guò)復(fù)制原型對(duì)象得到新實(shí)例可能比使用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例更加方便宪塔。