对象的克隆——原型模式
本文轉(zhuǎn)載自 :http://blog.csdn.net/lovelion/article/details/7424559
張紀(jì)中版《西游記》以出乎意料的造型和雷人的臺(tái)詞遭到廣大觀眾朋友的熱議,我們?cè)诖藢?duì)該話題不作過(guò)多討論。但無(wú)論是哪個(gè)版本的《西游記》,孫悟空都是其中的一號(hào)雄性主角,關(guān)于他(或它)拔毛變小猴的故事幾乎人人皆知,孫悟空可以用猴毛根據(jù)自己的形象,復(fù)制(又稱“克隆”或“拷貝”)出很多跟自己長(zhǎng)得一模一樣的“身外身”來(lái)。在設(shè)計(jì)模式中也存在一個(gè)類似的模式,可以通過(guò)一個(gè)原型對(duì)象克隆出多個(gè)一模一樣的對(duì)象,該模式稱之為原型模式。
7.1?大同小異的工作周報(bào)?
| Sunny軟件公司一直使用自行開(kāi)發(fā)的一套OA (Office Automatic,辦公自動(dòng)化)系統(tǒng)進(jìn)行日常工作辦理,但在使用過(guò)程中,越來(lái)越多的人對(duì)工作周報(bào)的創(chuàng)建和編寫(xiě)模塊產(chǎn)生了抱怨。追其原因,Sunny軟件公司的OA管理員發(fā)現(xiàn),由于某些崗位每周工作存在重復(fù)性,工作周報(bào)內(nèi)容都大同小異,如圖7-1工作周報(bào)示意圖。這些周報(bào)只有一些小地方存在差異,但是現(xiàn)行系統(tǒng)每周默認(rèn)創(chuàng)建的周報(bào)都是空白報(bào)表,用戶只能通過(guò)重新輸入或不斷復(fù)制粘貼來(lái)填寫(xiě)重復(fù)的周報(bào)內(nèi)容,極大降低了工作效率,浪費(fèi)寶貴的時(shí)間。如何快速創(chuàng)建相同或者相似的工作周報(bào),成為Sunny公司OA開(kāi)發(fā)人員面臨的一個(gè)新問(wèn)題。
圖7-1?工作周報(bào)示意圖 |
?????? Sunny公司的開(kāi)發(fā)人員通過(guò)對(duì)問(wèn)題進(jìn)行仔細(xì)分析,決定按照如下思路對(duì)工作周報(bào)模塊進(jìn)行重新設(shè)計(jì)和實(shí)現(xiàn):
?????? (1)除了允許用戶創(chuàng)建新周報(bào)外,還允許用戶將創(chuàng)建好的周報(bào)保存為模板;
?????? (2)用戶在再次創(chuàng)建周報(bào)時(shí),可以創(chuàng)建全新的周報(bào),還可以選擇合適的模板復(fù)制生成一份相同的周報(bào),然后對(duì)新生成的周報(bào)根據(jù)實(shí)際情況進(jìn)行修改,產(chǎn)生新的周報(bào)。
???????只要按照如上兩個(gè)步驟進(jìn)行處理,工作周報(bào)的創(chuàng)建效率將得以大大提高。這個(gè)過(guò)程讓我們想到平時(shí)經(jīng)常進(jìn)行的兩個(gè)電腦基本操作:復(fù)制和粘貼,快捷鍵通常為Ctrl + C和Ctrl + V,通過(guò)對(duì)已有對(duì)象的復(fù)制和粘貼,我們可以創(chuàng)建大量的相同對(duì)象。如何在一個(gè)面向?qū)ο笙到y(tǒng)中實(shí)現(xiàn)對(duì)象的復(fù)制和粘貼呢?不用著急,本章我們介紹的原型模式正為解決此類問(wèn)題而誕生。
7.2 原型模式概述
????? 在使用原型模式時(shí),我們需要首先創(chuàng)建一個(gè)原型對(duì)象,再通過(guò)復(fù)制這個(gè)原型對(duì)象來(lái)創(chuàng)建更多同類型的對(duì)象。試想,如果連孫悟空的模樣都不知道,怎么拔毛變小猴子呢?原型模式的定義如下:
| 原型模式(Prototype? Pattern):使用原型實(shí)例指定創(chuàng)建對(duì)象的種類,并且通過(guò)拷貝這些原型創(chuàng)建新的對(duì)象。原型模式是一種對(duì)象創(chuàng)建型模式。 |
????? 原型模式的工作原理很簡(jiǎn)單:將一個(gè)原型對(duì)象傳給那個(gè)要發(fā)動(dòng)創(chuàng)建的對(duì)象,這個(gè)要發(fā)動(dòng)創(chuàng)建的對(duì)象通過(guò)請(qǐng)求原型對(duì)象拷貝自己來(lái)實(shí)現(xiàn)創(chuàng)建過(guò)程。由于在軟件系統(tǒng)中我們經(jīng)常會(huì)遇到需要?jiǎng)?chuàng)建多個(gè)相同或者相似對(duì)象的情況,因此原型模式在真實(shí)開(kāi)發(fā)中的使用頻率還是非常高的。原型模式是一種“另類”的創(chuàng)建型模式,創(chuàng)建克隆對(duì)象的工廠就是原型類自身,工廠方法由克隆方法來(lái)實(shí)現(xiàn)。
??????需要注意的是通過(guò)克隆方法所創(chuàng)建的對(duì)象是全新的對(duì)象,它們?cè)趦?nèi)存中擁有新的地址,通常對(duì)克隆所產(chǎn)生的對(duì)象進(jìn)行修改對(duì)原型對(duì)象不會(huì)造成任何影響,每一個(gè)克隆對(duì)象都是相互獨(dú)立的。通過(guò)不同的方式修改可以得到一系列相似但不完全相同的對(duì)象。
?????? 原型模式的結(jié)構(gòu)如圖7-2所示:
圖7-2?原型模式結(jié)構(gòu)圖
????? 在原型模式結(jié)構(gòu)圖中包含如下幾個(gè)角色:
????? ●Prototype(抽象原型類):它是聲明克隆方法的接口,是所有具體原型類的公共父類,可以是抽象類也可以是接口,甚至還可以是具體實(shí)現(xiàn)類。
????? ●?ConcretePrototype(具體原型類):它實(shí)現(xiàn)在抽象原型類中聲明的克隆方法,在克隆方法中返回自己的一個(gè)克隆對(duì)象。
????? ●?Client(客戶類):讓一個(gè)原型對(duì)象克隆自身從而創(chuàng)建一個(gè)新的對(duì)象,在客戶類中只需要直接實(shí)例化或通過(guò)工廠方法等方式創(chuàng)建一個(gè)原型對(duì)象,再通過(guò)調(diào)用該對(duì)象的克隆方法即可得到多個(gè)相同的對(duì)象。由于客戶類針對(duì)抽象原型類Prototype編程,因此用戶可以根據(jù)需要選擇具體原型類,系統(tǒng)具有較好的可擴(kuò)展性,增加或更換具體原型類都很方便。
????? 原型模式的核心在于如何實(shí)現(xiàn)克隆方法,下面將介紹兩種在Java語(yǔ)言中常用的克隆實(shí)現(xiàn)方法:
1.通用實(shí)現(xiàn)方法
????? 通用的克隆實(shí)現(xiàn)方法是在具體原型類的克隆方法中實(shí)例化一個(gè)與自身類型相同的對(duì)象并將其返回,并將相關(guān)的參數(shù)傳入新創(chuàng)建的對(duì)象中,保證它們的成員屬性相同。示意代碼如下所示:
| 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(); //創(chuàng)建新對(duì)象 ??? prototype.setAttr(this.attr); ??? return prototype; } } |
|
????? 在客戶類中我們只需要?jiǎng)?chuàng)建一個(gè)ConcretePrototype對(duì)象作為原型對(duì)象,然后調(diào)用其clone()方法即可得到對(duì)應(yīng)的克隆對(duì)象,如下代碼所示:
| Prototype obj1? = new ConcretePrototype(); obj1.setAttr("Sunny"); Prototype obj2? = obj1.clone(); |
????? 這種方法可作為原型模式的通用實(shí)現(xiàn),它與編程語(yǔ)言特性無(wú)關(guān),任何面向?qū)ο笳Z(yǔ)言都可以使用這種形式來(lái)實(shí)現(xiàn)對(duì)原型的克隆。
2. Java語(yǔ)言提供的clone()方法
????? 學(xué)過(guò)Java語(yǔ)言的人都知道,所有的Java類都繼承自java.lang.Object。事實(shí)上,Object類提供一個(gè)clone()方法,可以將一個(gè)Java對(duì)象復(fù)制一份。因此在Java中可以直接使用Object提供的clone()方法來(lái)實(shí)現(xiàn)對(duì)象的克隆,Java語(yǔ)言中的原型模式實(shí)現(xiàn)很簡(jiǎn)單。
????? 需要注意的是能夠?qū)崿F(xiàn)克隆的Java類必須實(shí)現(xiàn)一個(gè)標(biāo)識(shí)接口Cloneable,表示這個(gè)Java類支持被復(fù)制。如果一個(gè)類沒(méi)有實(shí)現(xiàn)這個(gè)接口但是調(diào)用了clone()方法,Java編譯器將拋出一個(gè)CloneNotSupportedException異常。如下代碼所示:
| class ConcretePrototype implements? Cloneable { …… public Prototype? clone() { Object object = null; try { object = super.clone(); } catch (CloneNotSupportedException exception) { System.err.println("Not support cloneable"); } return (Prototype )object; } …… } |
????? 在客戶端創(chuàng)建原型對(duì)象和克隆對(duì)象也很簡(jiǎn)單,如下代碼所示:
| Prototype obj1? = new ConcretePrototype(); Prototype obj2? = obj1.clone(); |
????? 一般而言,Java語(yǔ)言中的clone()方法滿足:
(1)?對(duì)任何對(duì)象x,都有x.clone() != x,即克隆對(duì)象與原型對(duì)象不是同一個(gè)對(duì)象;
(2)?對(duì)任何對(duì)象x,都有x.clone().getClass() == x.getClass(),即克隆對(duì)象與原型對(duì)象的類型一樣;
(3)?如果對(duì)象x的equals()方法定義恰當(dāng),那么x.clone().equals(x)應(yīng)該成立。
??????為了獲取對(duì)象的一份拷貝,我們可以直接利用Object類的clone()方法,具體步驟如下:
(1)?在派生類中覆蓋基類的clone()方法,并聲明為public;
(2)?在派生類的clone()方法中,調(diào)用super.clone();
(3)派生類需實(shí)現(xiàn)Cloneable接口。
????? 此時(shí),Object類相當(dāng)于抽
7.3?完整解決方案
????? Sunny公司開(kāi)發(fā)人員決定使用原型模式來(lái)實(shí)現(xiàn)工作周報(bào)的快速創(chuàng)建,快速創(chuàng)建工作周報(bào)結(jié)構(gòu)圖如圖7-3所示:
圖7-3?快速創(chuàng)建工作周報(bào)結(jié)構(gòu)圖
????? 在圖7-3中,WeeklyLog充當(dāng)具體原型類,Object類充當(dāng)抽象原型類,clone()方法為原型方法。WeeklyLog類的代碼如下所示:
| //工作周報(bào)WeeklyLog:具體原型類,考慮到代碼的可讀性和易理解性,只列出部分與模式相關(guān)的核心代碼 class WeeklyLog implements Cloneable { ?????? private? String name; ?????? private? String date; ?????? private? String content; ?????? 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? String getName() { ????????????? return? (this.name); ?????? } ?????? public? String getDate() { ????????????? return? (this.date); ?????? } ?????? public? String getContent() { ????????????? return? (this.content); ?????? } ???? //克隆方法clone(),此處使用Java語(yǔ)言提供的克隆機(jī)制 ?????? public WeeklyLog clone() ?????? { ????????????? Object obj = null; ????????????? try ????????????? { ???????????????????? obj = super.clone(); ???????????????????? return (WeeklyLog)obj;????? ????????????? } ????????????? catch(CloneNotSupportedException e) ????????????? { ???????????????????? System.out.println("不支持復(fù)制!"); ???????????????????? return null; ????????????? } ?????? } } |
編寫(xiě)如下客戶端測(cè)試代碼:
| class Client { ?????? public? static void main(String args[]) ?????? { ??????????????WeeklyLog log_previous = new WeeklyLog();? //創(chuàng)建原型對(duì)象 ????????????? log_previous.setName("張無(wú)忌"); ????????????? log_previous.setDate("第12周"); ????????????? log_previous.setContent("這周工作很忙,每天加班!"); ????????????? ????????????? System.out.println("****周報(bào)****"); ????????????? System.out.println("周次:" +? log_previous.getDate()); ????????????? System.out.println("姓名:" +? log_previous.getName()); ????????????? System.out.println("內(nèi)容:" +? log_previous.getContent()); ????????????? System.out.println("--------------------------------"); ????????????? ????????????? WeeklyLog? log_new; ????????????? log_new? = log_previous.clone(); //調(diào)用克隆方法創(chuàng)建克隆對(duì)象 ????????????? log_new.setDate("第13周"); ????????????? System.out.println("****周報(bào)****"); ????????????? System.out.println("周次:" + log_new.getDate()); ????????????? System.out.println("姓名:" + log_new.getName()); ????????????? System.out.println("內(nèi)容:" + log_new.getContent()); ?????? } } |
????? 編譯并運(yùn)行程序,輸出結(jié)果如下:
| ****周報(bào)**** 周次:第12周 姓名:張無(wú)忌 內(nèi)容:這周工作很忙,每天加班! -------------------------------- ****周報(bào)**** 周次:第13周 姓名:張無(wú)忌 內(nèi)容:這周工作很忙,每天加班! |
????? 通過(guò)已創(chuàng)建的工作周報(bào)可以快速創(chuàng)建新的周報(bào),然后再根據(jù)需要修改周報(bào),無(wú)須再?gòu)念^開(kāi)始創(chuàng)建。原型模式為工作流系統(tǒng)中任務(wù)單的快速生成提供了一種解決方案。
|
7.4?帶附件的周報(bào)
????? 通過(guò)引入原型模式,Sunny軟件公司OA系統(tǒng)支持工作周報(bào)的快速克隆,極大提高了工作周報(bào)的編寫(xiě)效率,受到員工的一致好評(píng)。但有員工又發(fā)現(xiàn)一個(gè)問(wèn)題,有些工作周報(bào)帶有附件,例如經(jīng)理助理“小龍女”的周報(bào)通常附有本周項(xiàng)目進(jìn)展報(bào)告匯總表、本周客戶反饋信息匯總表等,如果使用上述原型模式來(lái)復(fù)制周報(bào),周報(bào)雖然可以復(fù)制,但是周報(bào)的附件并不能復(fù)制,這是由于什么原因?qū)е碌哪?#xff1f;如何才能實(shí)現(xiàn)周報(bào)和附件的同時(shí)復(fù)制呢?我們?cè)诒竟?jié)將討論如何解決這些問(wèn)題。
????? 在回答這些問(wèn)題之前,先介紹一下兩種不同的克隆方法,淺克隆(ShallowClone)和深克隆(DeepClone)。在Java語(yǔ)言中,數(shù)據(jù)類型分為值類型(基本數(shù)據(jù)類型)和引用類型,值類型包括int、double、byte、boolean、char等簡(jiǎn)單數(shù)據(jù)類型,引用類型包括類、接口、數(shù)組等復(fù)雜類型。淺克隆和深克隆的主要區(qū)別在于是否支持引用類型的成員變量的復(fù)制,下面將對(duì)兩者進(jìn)行詳細(xì)介紹。
?
1.淺克隆
????? 在淺克隆中,如果原型對(duì)象的成員變量是值類型,將復(fù)制一份給克隆對(duì)象;如果原型對(duì)象的成員變量是引用類型,則將引用對(duì)象的地址復(fù)制一份給克隆對(duì)象,也就是說(shuō)原型對(duì)象和克隆對(duì)象的成員變量指向相同的內(nèi)存地址。簡(jiǎn)單來(lái)說(shuō),在淺克隆中,當(dāng)對(duì)象被復(fù)制時(shí)只復(fù)制它本身和其中包含的值類型的成員變量,而引用類型的成員對(duì)象并沒(méi)有復(fù)制,如圖7-4所示:
圖7-4?淺克隆示意圖
????? 在Java語(yǔ)言中,通過(guò)覆蓋Object類的clone()方法可以實(shí)現(xiàn)淺克隆。為了讓大家更好地理解淺克隆和深克隆的區(qū)別,我們首先使用淺克隆來(lái)實(shí)現(xiàn)工作周報(bào)和附件類的復(fù)制,其結(jié)構(gòu)如圖7-5所示:
圖7-5?帶附件的周報(bào)結(jié)構(gòu)圖(淺克隆)
????? 附件類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); ???? } } |
????? 修改工作周報(bào)類WeeklyLog,修改后的代碼如下:
| //工作周報(bào)WeeklyLog class WeeklyLog implements Cloneable { ???? //為了簡(jiǎn)化設(shè)計(jì)和實(shí)現(xiàn),假設(shè)一份工作周報(bào)中只有一個(gè)附件對(duì)象,實(shí)際情況中可以包含多個(gè)附件,可以通過(guò)List等集合對(duì)象來(lái)實(shí)現(xiàn) ???????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()方法實(shí)現(xiàn)淺克隆 ?????? public WeeklyLog clone() ?????? { ????????????? Object obj = null; ????????????? try ????????????? { ???????????????????? obj = super.clone(); ???????????????????? return (WeeklyLog)obj; ????????????? } ????????????? catch(CloneNotSupportedException? e) ????????????? { ???????????????????? System.out.println("不支持復(fù)制!"); ???????????????????? return null; ????????????? } ?????? } } |
????? 客戶端代碼如下所示:
| class Client { ?????? public? static void main(String args[]) ?????? { ????????????? WeeklyLog? log_previous, log_new; ????????????? log_previous? = new WeeklyLog(); //創(chuàng)建原型對(duì)象 ????????????? Attachment? attachment = new Attachment(); //創(chuàng)建附件對(duì)象 ????????????? log_previous.setAttachment(attachment);? //將附件添加到周報(bào)中 ????????????? log_new? = log_previous.clone(); //調(diào)用克隆方法創(chuàng)建克隆對(duì)象 ????????????? //比較周報(bào) ????????????? System.out.println("周報(bào)是否相同??" + (log_previous ==? log_new)); ????????????? //比較附件 ????????????? System.out.println("附件是否相同??" +? (log_previous.getAttachment() == log_new.getAttachment())); ?????? } } |
????? 編譯并運(yùn)行程序,輸出結(jié)果如下:
| 周報(bào)是否相同?? false 附件是否相同??true |
???????由于使用的是淺克隆技術(shù),因此工作周報(bào)對(duì)象復(fù)制成功,通過(guò)“==”比較原型對(duì)象和克隆對(duì)象的內(nèi)存地址時(shí)輸出false;但是比較附件對(duì)象的內(nèi)存地址時(shí)輸出true,說(shuō)明它們?cè)趦?nèi)存中是同一個(gè)對(duì)象。
?
2.深克隆
????? 在深克隆中,無(wú)論原型對(duì)象的成員變量是值類型還是引用類型,都將復(fù)制一份給克隆對(duì)象,深克隆將原型對(duì)象的所有引用對(duì)象也復(fù)制一份給克隆對(duì)象。簡(jiǎn)單來(lái)說(shuō),在深克隆中,除了對(duì)象本身被復(fù)制外,對(duì)象所包含的所有成員變量也將復(fù)制,如圖7-6所示:
圖7-6?深克隆示意圖
????? 在Java語(yǔ)言中,如果需要實(shí)現(xiàn)深克隆,可以通過(guò)序列化(Serialization)等方式來(lái)實(shí)現(xiàn)。序列化就是將對(duì)象寫(xiě)到流的過(guò)程,寫(xiě)到流中的對(duì)象是原有對(duì)象的一個(gè)拷貝,而原對(duì)象仍然存在于內(nèi)存中。通過(guò)序列化實(shí)現(xiàn)的拷貝不僅可以復(fù)制對(duì)象本身,而且可以復(fù)制其引用的成員對(duì)象,因此通過(guò)序列化將對(duì)象寫(xiě)到一個(gè)流中,再?gòu)牧骼飳⑵渥x出來(lái),可以實(shí)現(xiàn)深克隆。需要注意的是能夠?qū)崿F(xiàn)序列化的對(duì)象其類必須實(shí)現(xiàn)Serializable接口,否則無(wú)法實(shí)現(xiàn)序列化操作。下面我們使用深克隆技術(shù)來(lái)實(shí)現(xiàn)工作周報(bào)和附件對(duì)象的復(fù)制,由于要將附件對(duì)象和工作周報(bào)對(duì)象都寫(xiě)入流中,因此兩個(gè)類均需要實(shí)現(xiàn)Serializable接口,其結(jié)構(gòu)如圖7-7所示:
圖7-7?帶附件的周報(bào)結(jié)構(gòu)圖(深克隆)
????? 修改后的附件類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); ???? } } |
????? 工作周報(bào)類WeeklyLog不再使用Java自帶的克隆機(jī)制,而是通過(guò)序列化來(lái)從頭實(shí)現(xiàn)對(duì)象的深克隆,我們需要重新編寫(xiě)clone()方法,修改后的代碼如下:
| import? java.io.*;
//工作周報(bào)類 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); ?????? } ???//使用序列化技術(shù)實(shí)現(xiàn)深克隆 ?????? public WeeklyLog deepClone() throws? IOException, ClassNotFoundException, OptionalDataException ?????? { ????????????? //將對(duì)象寫(xiě)入流中 ????????????? ByteArrayOutputStream bao=new? ByteArrayOutputStream(); ????????????? ObjectOutputStream oos=new? ObjectOutputStream(bao); ????????????? oos.writeObject(this); ????????????? ????????????? //將對(duì)象從流中取出 ????????????? 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(); //創(chuàng)建原型對(duì)象 ????????????? Attachment? attachment = new Attachment(); //創(chuàng)建附件對(duì)象 ????????????? log_previous.setAttachment(attachment);? //將附件添加到周報(bào)中 ????????????? try ????????????? { ???????????????????? log_new =? log_previous.deepClone(); //調(diào)用深克隆方法創(chuàng)建克隆對(duì)象?????????????????? ????????????? } ????????????? catch(Exception e) ????????????? { ???????????????????? System.err.println("克隆失敗!"); ????????????? } ????????????? //比較周報(bào) ????????????? System.out.println("周報(bào)是否相同??" + (log_previous ==? log_new)); ????????????? //比較附件 ????????????? System.out.println("附件是否相同??" +? (log_previous.getAttachment() == log_new.getAttachment())); ?????? } } |
????? 編譯并運(yùn)行程序,輸出結(jié)果如下:
| 周報(bào)是否相同?? false 附件是否相同?? false |
???????從輸出結(jié)果可以看出,由于使用了深克隆技術(shù),附件對(duì)象也得以復(fù)制,因此用“==”比較原型對(duì)象的附件和克隆對(duì)象的附件時(shí)輸出結(jié)果均為false。深克隆技術(shù)實(shí)現(xiàn)了原型對(duì)象和克隆對(duì)象的完全獨(dú)立,對(duì)任意克隆對(duì)象的修改都不會(huì)給其他對(duì)象產(chǎn)生影響,是一種更為理想的克隆實(shí)現(xiàn)方式。
|
7.5?原型管理器的引入和實(shí)現(xiàn)
????? 原型管理器(Prototype Manager)是將多個(gè)原型對(duì)象存儲(chǔ)在一個(gè)集合中供客戶端使用,它是一個(gè)專門(mén)負(fù)責(zé)克隆對(duì)象的工廠,其中定義了一個(gè)集合用于存儲(chǔ)原型對(duì)象,如果需要某個(gè)原型對(duì)象的一個(gè)克隆,可以通過(guò)復(fù)制集合中對(duì)應(yīng)的原型對(duì)象來(lái)獲得。在原型管理器中針對(duì)抽象原型類進(jìn)行編程,以便擴(kuò)展。其結(jié)構(gòu)如圖7-8所示:
?????????????????????????????
圖7-8?帶原型管理器的原型模式
????? 下面通過(guò)模擬一個(gè)簡(jiǎn)單的公文管理器來(lái)介紹原型管理器的設(shè)計(jì)與實(shí)現(xiàn):
| Sunny軟件公司在日常辦公中有許多公文需要?jiǎng)?chuàng)建、遞交和審批,例如《可行性分析報(bào)告》、《立項(xiàng)建議書(shū)》、《軟件需求規(guī)格說(shuō)明書(shū)》、《項(xiàng)目進(jìn)展報(bào)告》等,為了提高工作效率,在OA系統(tǒng)中為各類公文均創(chuàng)建了模板,用戶可以通過(guò)這些模板快速創(chuàng)建新的公文,這些公文模板需要統(tǒng)一進(jìn)行管理,系統(tǒng)根據(jù)用戶請(qǐng)求的不同生成不同的新公文。 |
???????我們使用帶原型管理器的原型模式實(shí)現(xiàn)公文管理器的設(shè)計(jì),其結(jié)構(gòu)如圖7-9所示:
圖7-9?公文管理器結(jié)構(gòu)圖
????? 以下是實(shí)現(xiàn)該功能的一些核心代碼,考慮到代碼的可讀性,我們對(duì)所有的類都進(jìn)行了簡(jiǎn)化:
| import java.util.*; ? //抽象公文接口,也可定義為抽象類,提供clone()方法的實(shí)現(xiàn),將業(yè)務(wù)方法聲明為抽象方法 interface OfficialDocument extends? Cloneable { ?????? public? OfficialDocument clone(); ?????? public? void display(); } ? //可行性分析報(bào)告(Feasibility Analysis Report)類 class FAR implements OfficialDocument { ?????? public? OfficialDocument clone() ? ??? { ????????????? OfficialDocument? far = null; ????????????? try ????????????? { ???????????????????? far? = (OfficialDocument)super.clone(); ????????????? } ????????????? catch(CloneNotSupportedException? e) ????????????? { ???????????????????? ?System.out.println("不支持復(fù)制!"); ????????????? } ????????????? return? far; ?????? } ?????? ?????? public? void display() ?????? { ????????????? System.out.println("《可行性分析報(bào)告》"); ?????? } } ? //軟件需求規(guī)格說(shuō)明書(shū)(Software Requirements Specification)類 class SRS implements OfficialDocument { ?????? public? OfficialDocument clone() ?????? { ????????????? OfficialDocument? srs = null; ????????????? try ????????????? { ???????????????????? srs? = (OfficialDocument)super.clone(); ????????????? } ????????????? catch(CloneNotSupportedException? e) ????????????? {? ???????????????????? System.out.println("不支持復(fù)制!"); ????????????? } ????????????? return? srs; ?????? } ?????? ?????? public? void display() ?????? { ????????????? System.out.println("《軟件需求規(guī)格說(shuō)明書(shū)》"); ?????? } } ? //原型管理器(使用餓漢式單例實(shí)現(xiàn)) class? PrototypeManager { ?????? //定義一個(gè)Hashtable,用于存儲(chǔ)原型對(duì)象 ?????? private Hashtable ht=new Hashtable(); ?????? private static PrototypeManager pm =? new PrototypeManager(); ?????? ?????? //為Hashtable增加公文對(duì)象??? ?? ? private? PrototypeManager() ?? ? { ????????????? ht.put("far",new? FAR()); ????????????? ht.put("srs",new? SRS());?? ???????????? ?? ? } ?? ?? ? //增加新的公文對(duì)象 ?????? public void addOfficialDocument(String? key,OfficialDocument doc) ?????? { ????????????? ht.put(key,doc); ?????? } ? ?????? //通過(guò)淺克隆獲取新的公文對(duì)象 ?????? public OfficialDocument? getOfficialDocument(String key) ?????? { ????????????? return? ((OfficialDocument)ht.get(key)).clone(); ?????? } ?????? ?????? public static PrototypeManager? getPrototypeManager() ?????? { ????????????? return pm; ?????? } } |
????? 客戶端代碼如下所示:
| class Client { ?????? public? static void main(String args[]) ?????? { ????????????? //獲取原型管理器對(duì)象 ????????????? PrototypeManager pm =? PrototypeManager.getPrototypeManager();?? ????????????? ????????????? OfficialDocument? doc1,doc2,doc3,doc4; ????????????? ????????????? doc1? = pm.getOfficialDocument("far"); ????????????? doc1.display(); ????????????? doc2? = pm.getOfficialDocument("far"); ????????????? doc2.display(); ????????????? System.out.println(doc1? == doc2); ????????????? ????????????? doc3? = pm.getOfficialDocument("srs"); ????????????? doc3.display(); ????????????? doc4? = pm.getOfficialDocument("srs"); ????????????? doc4.display(); ????????????? System.out.println(doc3? == doc4); ?????? } } |
????? 編譯并運(yùn)行程序,輸出結(jié)果如下:
| 《可行性分析報(bào)告》 《可行性分析報(bào)告》 false 《軟件需求規(guī)格說(shuō)明書(shū)》 《軟件需求規(guī)格說(shuō)明書(shū)》 false |
????? 在PrototypeManager中定義了一個(gè)Hashtable類型的集合對(duì)象,使用“鍵值對(duì)”來(lái)存儲(chǔ)原型對(duì)象,客戶端可以通過(guò)Key(如“far”或“srs”)來(lái)獲取對(duì)應(yīng)原型對(duì)象的克隆對(duì)象。PrototypeManager類提供了類似工廠方法的getOfficialDocument()方法用于返回一個(gè)克隆對(duì)象。在本實(shí)例代碼中,我們將PrototypeManager設(shè)計(jì)為單例類,使用餓漢式單例實(shí)現(xiàn),確保系統(tǒng)中有且僅有一個(gè)PrototypeManager對(duì)象,有利于節(jié)省系統(tǒng)資源,并可以更好地對(duì)原型管理器對(duì)象進(jìn)行控制。
|
7.6 原型模式總結(jié)
????? 原型模式作為一種快速創(chuàng)建大量相同或相似對(duì)象的方式,在軟件開(kāi)發(fā)中應(yīng)用較為廣泛,很多軟件提供的復(fù)制(Ctrl + C)和粘貼(Ctrl + V)操作就是原型模式的典型應(yīng)用,下面對(duì)該模式的使用效果和適用情況進(jìn)行簡(jiǎn)單的總結(jié)。
1.主要優(yōu)點(diǎn)
????? 原型模式的主要優(yōu)點(diǎn)如下:
(1)?當(dāng)創(chuàng)建新的對(duì)象實(shí)例較為復(fù)雜時(shí),使用原型模式可以簡(jiǎn)化對(duì)象的創(chuàng)建過(guò)程,通過(guò)復(fù)制一個(gè)已有實(shí)例可以提高新實(shí)例的創(chuàng)建效率。
(2)?擴(kuò)展性較好,由于在原型模式中提供了抽象原型類,在客戶端可以針對(duì)抽象原型類進(jìn)行編程,而將具體原型類寫(xiě)在配置文件中,增加或減少產(chǎn)品類對(duì)原有系統(tǒng)都沒(méi)有任何影響。
(3)?原型模式提供了簡(jiǎn)化的創(chuàng)建結(jié)構(gòu),工廠方法模式常常需要有一個(gè)與產(chǎn)品類等級(jí)結(jié)構(gòu)相同的工廠等級(jí)結(jié)構(gòu),而原型模式就不需要這樣,原型模式中產(chǎn)品的復(fù)制是通過(guò)封裝在原型類中的克隆方法實(shí)現(xiàn)的,無(wú)須專門(mén)的工廠類來(lái)創(chuàng)建產(chǎn)品。
(4)?可以使用深克隆的方式保存對(duì)象的狀態(tài),使用原型模式將對(duì)象復(fù)制一份并將其狀態(tài)保存起來(lái),以便在需要的時(shí)候使用(如恢復(fù)到某一歷史狀態(tài)),可輔助實(shí)現(xiàn)撤銷操作。
2.主要缺點(diǎn)
????? 原型模式的主要缺點(diǎn)如下:
(1)?需要為每一個(gè)類配備一個(gè)克隆方法,而且該克隆方法位于一個(gè)類的內(nèi)部,當(dāng)對(duì)已有的類進(jìn)行改造時(shí),需要修改源代碼,違背了“開(kāi)閉原則”。
(2)?在實(shí)現(xiàn)深克隆時(shí)需要編寫(xiě)較為復(fù)雜的代碼,而且當(dāng)對(duì)象之間存在多重的嵌套引用時(shí),為了實(shí)現(xiàn)深克隆,每一層對(duì)象對(duì)應(yīng)的類都必須支持深克隆,實(shí)現(xiàn)起來(lái)可能會(huì)比較麻煩。
3.適用場(chǎng)景
??????? 在以下情況下可以考慮使用原型模式:
(1)?創(chuàng)建新對(duì)象成本較大(如初始化需要占用較長(zhǎng)的時(shí)間,占用太多的CPU資源或網(wǎng)絡(luò)資源),新的對(duì)象可以通過(guò)原型模式對(duì)已有對(duì)象進(jìn)行復(fù)制來(lái)獲得,如果是相似對(duì)象,則可以對(duì)其成員變量稍作修改。
(2)?如果系統(tǒng)要保存對(duì)象的狀態(tài),而對(duì)象的狀態(tài)變化很小,或者對(duì)象本身占用內(nèi)存較少時(shí),可以使用原型模式配合備忘錄模式來(lái)實(shí)現(xiàn)。
(3)?需要避免使用分層次的工廠類來(lái)創(chuàng)建分層次的對(duì)象,并且類的實(shí)例對(duì)象只有一個(gè)或很少的幾個(gè)組合狀態(tài),通過(guò)復(fù)制原型對(duì)象得到新實(shí)例可能比使用構(gòu)造函數(shù)創(chuàng)建一個(gè)新實(shí)例更加方便。
|
總結(jié)
以上是生活随笔為你收集整理的对象的克隆——原型模式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 扩展系统功能——装饰模式
- 下一篇: 请求的链式处理——职责链模式