国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 學(xué)院 > 開發(fā)設(shè)計(jì) > 正文

原型模式【Prototype Pattern】

2019-11-09 18:59:18
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

      今天我們來(lái)講原型模式,這個(gè)模式的簡(jiǎn)單程度是僅次于單例模式和迭代器模式,非常簡(jiǎn)單,但是要使用好這個(gè)模式還有很多注意事項(xiàng)。我們通過(guò)一個(gè)例子來(lái)解釋一下什么是原型模式。

        現(xiàn)在電子賬單越來(lái)越流行了,比如你的信用卡,到月初的時(shí)候銀行就會(huì)發(fā)一份電子郵件到你郵箱中,說(shuō)你這個(gè)月消費(fèi)了多少,什么時(shí)候消費(fèi)的,積分是多少等等,這個(gè)是每個(gè)月發(fā)一次,但是還有一種也是銀行發(fā)的郵件你肯定有印象:廣告信,現(xiàn)在各大銀行的信用卡部門都在拉攏客戶,電子郵件是一種廉價(jià)、快捷的通訊方式,你用紙質(zhì)的廣告信那個(gè)費(fèi)用多高呀,比如我今天推出一個(gè)信用卡刷卡抽獎(jiǎng)活動(dòng),通過(guò)電子賬單系統(tǒng)可以一個(gè)晚上發(fā)送給600 萬(wàn)客戶,為什么要用電子賬單系統(tǒng)呢?直接找個(gè)發(fā)垃圾郵件不就解決問(wèn)題了嗎?是個(gè)好主意,但是這個(gè)方案在金融行業(yè)是行不通的,銀行發(fā)這種郵件是有要求的,一是一般銀行都要求個(gè)性化服務(wù),發(fā)過(guò)去的郵件上總有一些個(gè)人信息吧,比如“XX 先生”,“XX 女士”等等,二是郵件的到達(dá)率有一定的要求,由于大批量的發(fā)送郵件會(huì)被接收方郵件服務(wù)器誤認(rèn)是垃圾郵件,因此在郵件頭要增加一些偽造數(shù)據(jù),以規(guī)避被反垃圾郵件引擎誤認(rèn)為是垃圾郵件;從這兩方面考慮廣告信的發(fā)送也是電子賬單系統(tǒng)(電子賬單系統(tǒng)一般包括:賬單分析、賬單生成器、廣告信管理、發(fā)送隊(duì)列管理、發(fā)送機(jī)、退信處理、報(bào)表管理等)的一個(gè)子功能,我們今天就來(lái)考慮一下廣告信這個(gè)模塊是怎么開發(fā)的。那既然是廣告信,肯定需要一個(gè)模版,然后再?gòu)?a href="http://m.survivalescaperooms.com/sql.asp">數(shù)據(jù)庫(kù)中把客戶的信息一個(gè)一個(gè)的取出,放到模版中生成一份完整的郵件,然后扔給發(fā)送機(jī)進(jìn)行發(fā)送處理,我們來(lái)看類圖:

       在類圖中AdvTemplate 是廣告信的模板,一般都是從數(shù)據(jù)庫(kù)取出,生成一個(gè)BO 或者是DTO,我們這里使用一個(gè)靜態(tài)的值來(lái)做代表;Mail 類是一封郵件類,發(fā)送機(jī)發(fā)送的就是這個(gè)類,我們先來(lái)看看我們的程序:

package Test;public class AdvTemplate {	// 廣告信名稱	PRivate String advSubject = "XX銀行國(guó)慶信用卡抽獎(jiǎng)活動(dòng)";	// 廣告信內(nèi)容	private String advContext = "國(guó)慶抽獎(jiǎng)活動(dòng)通知:只要刷卡就送你1百萬(wàn)!....";	// 取得廣告信的名稱	public String getAdvSubject() {		return this.advSubject;	}	// 取得廣告信的內(nèi)容	public String getAdvContext() {		return this.advContext;	}}我們?cè)賮?lái)看郵件類:

package Test;public class Mail {	// 收件人	private String receiver;	// 郵件名稱	private String subject;	// 稱謂	private String appellation;	// 郵件內(nèi)容	private String contxt;	// 郵件的尾部,一般都是加上“XXX版權(quán)所有”等信息	private String tail;	// 構(gòu)造函數(shù)	public Mail(AdvTemplate advTemplate) {		this.contxt = advTemplate.getAdvContext();		this.subject = advTemplate.getAdvSubject();	}	// 以下為getter/setter方法	public String getReceiver() {		return receiver;	}	public void setReceiver(String receiver) {		this.receiver = receiver;	}	public String getSubject() {		return subject;	}	public void setSubject(String subject) {		this.subject = subject;	}	public String getAppellation() {		return appellation;	}	public void setAppellation(String appellation) {		this.appellation = appellation;	}	public String getContxt() {		return contxt;	}	public void setContxt(String contxt) {		this.contxt = contxt;	}	public String getTail() {		return tail;	}	public void setTail(String tail) {		this.tail = tail;	}}    Mail 就是一個(gè)業(yè)務(wù)對(duì)象,我們?cè)賮?lái)看業(yè)務(wù)場(chǎng)景類是怎么調(diào)用的:

package Test;import java.util.Random;public class Client {	// 發(fā)送賬單的數(shù)量,這個(gè)值是從數(shù)據(jù)庫(kù)中獲得	private static int MAX_COUNT = 6;	public static void main(String[] args) {		// 模擬發(fā)送郵件		int i = 0;		// 把模板定義出來(lái),這個(gè)是從數(shù)據(jù)庫(kù)中獲得		Mail mail = new Mail(new AdvTemplate());		mail.setTail("XX銀行版權(quán)所有");		while (i < MAX_COUNT) {			// 以下是每封郵件不同的地方			mail.setAppellation(getRandString(5) + " 先生(女士)");			mail.setReceiver(getRandString(5) + "@" + getRandString(8) + ".com");			// 然后發(fā)送郵件			sendMail(mail);			i++;		}	}	// 發(fā)送郵件	public static void sendMail(Mail mail) {		System.out.println("標(biāo)題:" + mail.getSubject() + "/t收件人"				+ mail.getReceiver() + "/t....發(fā)送成功!");	}	// 獲得指定長(zhǎng)度的隨機(jī)字符串	public static String getRandString(int maxLength) {		String source = "abcdefghijklmnopqrskuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";		StringBuffer sb = new StringBuffer();		Random rand = new Random();		for (int i = 0; i < maxLength; i++) {			sb.append(source.charAt(rand.nextInt(source.length())));		}		return sb.toString();	}}

運(yùn)行結(jié)果如下:

       由于是隨機(jī)數(shù),每次運(yùn)行都由所差異,不管怎么樣,我們這個(gè)電子賬單發(fā)送程序時(shí)寫出來(lái)了,也能發(fā)送出來(lái)了,我們?cè)賮?lái)仔細(xì)的想想,這個(gè)程序是否有問(wèn)題?你看,你這是一個(gè)線程在運(yùn)行,也就是你發(fā)送是單線程的,那按照一封郵件發(fā)出去需要0.02 秒(夠小了,你還要到數(shù)據(jù)庫(kù)中取數(shù)據(jù)呢),600 萬(wàn)封郵件需要…我算算(掰指頭計(jì)算中…),恩,是33 個(gè)小時(shí),也就是一個(gè)整天都發(fā)送不完畢,今天發(fā)送不完畢,明天的賬單又產(chǎn)生了,積累積累,激起甲方人員一堆抱怨,那怎么辦?.

       好辦,把sendMail 修改為多線程,但是你只把sendMail 修改為多線程還是有問(wèn)題的呀,你看哦,產(chǎn)生第一封郵件對(duì)象,放到線程1 中運(yùn)行,還沒(méi)有發(fā)送出去;線程2 呢也也啟動(dòng)了,直接就把郵件對(duì)象mail的收件人地址和稱謂修改掉了,線程不安全了,好了,說(shuō)到這里,你會(huì)說(shuō)這有N 多種解決辦法,我們不多說(shuō),我們今天就說(shuō)一種,使用原型模式來(lái)解決這個(gè)問(wèn)題,使用對(duì)象的拷貝功能來(lái)解決這個(gè)問(wèn)題,類圖稍作修改,如下圖:

增加了一個(gè)Cloneable 接口, Mail 實(shí)現(xiàn)了這個(gè)接口,在Mail 類中重寫了clone()方法,我們來(lái)看Mail類的改變:

package Test;public class Mail implements Cloneable{	//收件人	private String receiver;	//郵件名稱	private String subject;	//稱謂	private String appellation;	//郵件內(nèi)容	private String contxt;	//郵件的尾部,一般都是加上“XXX版權(quán)所有”等信息	private String tail;	//構(gòu)造函數(shù)	public Mail(AdvTemplate advTemplate){	this.contxt = advTemplate.getAdvContext();	this.subject = advTemplate.getAdvSubject();	}	@Override	public Mail clone(){	Mail mail =null;	try {	mail = (Mail)super.clone();	} catch (CloneNotSupportedException e) {	// TODO Auto-generated catch block	e.printStackTrace();	}	return mail;	}	//以下為getter/setter方法	public String getReceiver() {	return receiver;	}	public void setReceiver(String receiver) {	this.receiver = receiver;	}	public String getSubject() {	return subject;	}	public void setSubject(String subject) {	this.subject = subject;	}	public String getAppellation() {	return appellation;	}	public void setAppellation(String appellation) {	this.appellation = appellation;	}	public String getContxt() {	return contxt;	}	public void setContxt(String contxt) {	this.contxt = contxt;	}	public String getTail() {	return tail;	}	public void setTail(String tail) {	this.tail = tail;	}}      就黃色體部分做了修改,大家可能看著這個(gè)類有點(diǎn)奇怪,先保留你的好奇,我們繼續(xù)講下去,我會(huì)給你解答的,看Client 類的改變:

package Test;import java.util.Random;public class Client {	// 發(fā)送賬單的數(shù)量,這個(gè)值是從數(shù)據(jù)庫(kù)中獲得	private static int MAX_COUNT = 6;	public static void main(String[] args) {		// 模擬發(fā)送郵件		int i = 0;		// 把模板定義出來(lái),這個(gè)是從數(shù)據(jù)中獲得		Mail mail = new Mail(new AdvTemplate());		mail.setTail("XX銀行版權(quán)所有");		while (i < MAX_COUNT) {			// 以下是每封郵件不同的地方			Mail cloneMail = mail.clone();			cloneMail.setAppellation(getRandString(5) + " 先生(女士)");			cloneMail.setReceiver(getRandString(5) + "@" + getRandString(8)					+ ".com");			// 然后發(fā)送郵件			sendMail(cloneMail);			i++;		}	}}

       運(yùn)行結(jié)果不變,一樣完成了電子廣告信的發(fā)送功能,而且sendMail 即使是多線程也沒(méi)有關(guān)系,看到mail.clone()這個(gè)方法了嗎?把對(duì)象拷貝一份,產(chǎn)生一個(gè)新的對(duì)象,和原有對(duì)象一樣,然后再修改細(xì)節(jié)的數(shù)據(jù),如設(shè)置稱謂,設(shè)置收件人地址等等。這種不通過(guò)new 關(guān)鍵字來(lái)產(chǎn)生一個(gè)對(duì)象,而是通過(guò)對(duì)象拷貝來(lái)實(shí)現(xiàn)的模式就叫做原型模式,其通用類圖如下:

       這個(gè)模式的核心是一個(gè)clone 方法,通過(guò)這個(gè)方法進(jìn)行對(duì)象的拷貝,Java 提供了一個(gè)Cloneable 接口來(lái)標(biāo)示這個(gè)對(duì)象是可拷貝的,為什么說(shuō)是“標(biāo)示”呢?翻開JDK 的幫助看看Cloneable 是一個(gè)方法都沒(méi)有的,這個(gè)接口只是一個(gè)標(biāo)記作用,在JVM 中具有這個(gè)標(biāo)記的對(duì)象才有可能被拷貝,那怎么才能從“有可能被拷貝”轉(zhuǎn)換為“可以被拷貝”呢?方法是覆蓋clone()方法,是的,你沒(méi)有看錯(cuò)是重寫clone()方法,看看我們上面Mail 類:

              @Override           public Mail clone(){   }

      在 clone()方法上增加了一個(gè)注解@Override,沒(méi)有繼承一個(gè)類為什么可以重寫呢?在Java 中所有類的老祖宗是誰(shuí)?對(duì)嘛,Object 類,每個(gè)類默認(rèn)都是繼承了這個(gè)類,所以這個(gè)用上重寫是非常正確的。原型模式雖然很簡(jiǎn)單,但是在Java 中使用原型模式也就是clone 方法還是有一些注意事項(xiàng)的,我們通過(guò)幾個(gè)例子一個(gè)一個(gè)解說(shuō)(如果你對(duì)Java 不是很感冒的話,可以跳開以下部分)。對(duì)象拷貝時(shí),類的構(gòu)造函數(shù)是不會(huì)被執(zhí)行的。一個(gè)實(shí)現(xiàn)了Cloneable 并重寫了clone 方法的類A,有一個(gè)無(wú)參構(gòu)造或有參構(gòu)造B,通過(guò)new 關(guān)鍵字產(chǎn)生了一個(gè)對(duì)象S,再然后通過(guò)S.clone()方式產(chǎn)生了一個(gè)新的對(duì)象T,那么在對(duì)象拷貝時(shí)構(gòu)造函數(shù)B 是不會(huì)被執(zhí)行的,我們來(lái)寫一小段程序來(lái)說(shuō)明這個(gè)問(wèn)題:

package Test;public class Thing implements Cloneable {	public Thing() {		System.out.println("構(gòu)造函數(shù)被執(zhí)行了...");	}	@Override	public Thing clone() {		Thing thing = null;		try {			thing = (Thing) super.clone();		} catch (CloneNotSupportedException e) {			e.printStackTrace();		}		return thing;	}}

   然后我們?cè)賮?lái)寫一個(gè)Client 類,進(jìn)行對(duì)象的拷貝:

package Test;public class Client1 {	public static void main(String[] args) {		// 產(chǎn)生一個(gè)對(duì)象		Thing thing = new Thing();		// 拷貝一個(gè)對(duì)象		Thing cloneThing = thing.clone();	}}運(yùn)行結(jié)果如下:

       對(duì)象拷貝時(shí)確實(shí)構(gòu)造函數(shù)沒(méi)有被執(zhí)行,這個(gè)從原理來(lái)講也是可以講得通的,Object 類的clone 方法的原理是從內(nèi)存中(具體的說(shuō)就是堆內(nèi)存)以二進(jìn)制流的方式進(jìn)行拷貝,重新分配一個(gè)內(nèi)存塊,那構(gòu)造函數(shù)沒(méi)有被執(zhí)行也是非常正常的了。

  淺拷貝和深拷貝問(wèn)題。在解釋什么是淺拷貝什么是拷貝前,我們先來(lái)看個(gè)例子:

package Test;public class Thing implements Cloneable {	// 定義一個(gè)私有變量	private ArrayList<String> arrayList = new ArrayList<String>();	@Override	public Thing clone() {		Thing thing = null;		try {			thing = (Thing) super.clone();		} catch (CloneNotSupportedException e) {			e.printStackTrace();		}		return thing;	}	// 設(shè)置HashMap的值	public void setValue(String value) {		this.arrayList.add(value);	}	// 取得arrayList的值	public ArrayList<String> getValue() {		return this.arrayList;	}}     在Thing 類中增加一個(gè)私有變量arrayLis,類型為ArrayList,然后通過(guò)setValue 和getValue 分別進(jìn)行設(shè)置和取值,我們來(lái)看場(chǎng)景類:

package Test;import java.util.Random;public class Client {	public static void main(String[] args) {		// 產(chǎn)生一個(gè)對(duì)象		Thing thing = new Thing();		// 設(shè)置一個(gè)值		thing.setValue("張三");		// 拷貝一個(gè)對(duì)象		Thing cloneThing = thing.clone();		cloneThing.setValue("李四");		System.out.println(thing.getValue());	}}

大家猜想一下運(yùn)行結(jié)果應(yīng)該是什么?是就一個(gè)“張三”嗎?運(yùn)行結(jié)果如下:

      怎么會(huì)有李四呢?是因?yàn)镴ava 做了一個(gè)偷懶的拷貝動(dòng)作,Object 類提供的方法clone 只是拷貝本對(duì)象,其對(duì)象內(nèi)部的數(shù)組、引用對(duì)象等都不拷貝,還是指向原生對(duì)象的內(nèi)部元素地址,這種拷貝就叫做淺拷貝,確實(shí)是非常淺,兩個(gè)對(duì)象共享了一個(gè)私有變量,你改我改大家都能改,是一個(gè)種非常不安全的方式,在實(shí)際項(xiàng)目中使用還是比較少的。你可能會(huì)比較奇怪,為什么在Mail 那個(gè)類中就可以使用使用String 類型,而不會(huì)產(chǎn)生由淺拷貝帶來(lái)的問(wèn)題呢??jī)?nèi)部的數(shù)組和引用對(duì)象才不拷貝,其他的原始類型比如int,long,String(Java 就希望你把String 認(rèn)為是基本類型,String 是沒(méi)有clone 方法的)等都會(huì)被拷貝的。

淺拷貝是有風(fēng)險(xiǎn)的,那怎么才能深入的拷貝呢?我們修改一下我們的程序:

package Test;	public class Thing implements Cloneable{		//定義一個(gè)私有變量		private ArrayList<String> arrayList = new ArrayList<String>();		@Override		public Thing clone(){		Thing thing=null;		try {		thing = (Thing)super.clone();		thing.arrayList = (ArrayList<String>)this.arrayList.clone();		} catch (CloneNotSupportedException e) {		e.printStackTrace();		}		return thing;		}		}}

僅僅增加了黃色部分,Client 類沒(méi)有任何改變,運(yùn)行結(jié)果如下:

     這個(gè)實(shí)現(xiàn)了完全的拷貝,兩個(gè)對(duì)象之間沒(méi)有任何的瓜葛了,你修改你的,我修改我的,不相互影響,這種拷貝就叫做深拷貝,深拷貝還有一種實(shí)現(xiàn)方式就是通過(guò)自己寫二進(jìn)制流來(lái)操作對(duì)象,然后實(shí)現(xiàn)對(duì)象的深拷貝,這個(gè)大家有時(shí)間自己實(shí)現(xiàn)一下。

     深拷貝和淺拷貝建議不要混合使用,一個(gè)類中某些引用使用深拷貝某些引用使用淺拷貝,這是一種非常差的設(shè)計(jì),特別是是在涉及到類的繼承,父類有幾個(gè)引用的情況就非常的復(fù)雜,建議的方案深拷貝和淺拷貝分開實(shí)現(xiàn)。

Clone 與final 兩對(duì)冤家。對(duì)象的clone 與對(duì)象內(nèi)的final 屬性是由沖突的,我們舉例來(lái)說(shuō)明這個(gè)問(wèn)題:

package Test;	public class Thing implements Cloneable {		// 定義一個(gè)私有變量		private final ArrayList<String> arrayList = new ArrayList<String>();		@Override		public Thing clone() {			Thing thing = null;			try {				thing = (Thing) super.clone();				this.arrayList = (ArrayList<String>) this.arrayList.clone();			} catch (CloneNotSupportedException e) {				e.printStackTrace();			}			return thing;		}	}}

       黃色的部分僅僅增加了一個(gè)final 關(guān)鍵字,然后編譯器就報(bào)灰色的部分錯(cuò)誤,正常呀,final 類型你還想重寫設(shè)值呀!完蛋了,你要實(shí)現(xiàn)深拷貝的夢(mèng)想在final 關(guān)鍵字的威脅下破滅了,路總是有的,我們來(lái)想想怎么修改這個(gè)方法:刪除掉final 關(guān)鍵字,這是最便捷最安全最快速的方式,你要使用clone 方法就在類的成員變量上不要增加final 關(guān)鍵字。

        原型模式適合在什么場(chǎng)景使用?一是類初始化需要消化非常多的資源,這個(gè)資源包括數(shù)據(jù)、硬件資源等;二是通過(guò)new 產(chǎn)生一個(gè)對(duì)象需要非常繁瑣的數(shù)據(jù)準(zhǔn)備或訪問(wèn)權(quán)限,則可以使用原型模式;三是一個(gè)對(duì)象需要提供給其他對(duì)象訪問(wèn),而且各個(gè)調(diào)用者可能都需要修改其值時(shí),可以考慮使用原型模式拷貝多個(gè)對(duì)象供調(diào)用者使用。在實(shí)際項(xiàng)目中,原型模式很少單獨(dú)出現(xiàn),一般是和工廠方法模式一起出現(xiàn),通過(guò)clone的方法創(chuàng)建一個(gè)對(duì)象,然后由工廠方法提供給調(diào)用者。

     原型模式先產(chǎn)生出一個(gè)包含大量共有信息的類,然后可以拷貝出副本,修正細(xì)節(jié)信息,建立了一個(gè)完整的個(gè)性對(duì)象。不知道大家有沒(méi)有看過(guò)施瓦辛格演的《第六日》這個(gè)電影,電影的主線也就是一個(gè)人被復(fù)制,然后正本和副本對(duì)掐,我們今天講的原型模式也就是由一個(gè)正本可以創(chuàng)建多個(gè)副本的概念,可以這樣理解一個(gè)對(duì)象的產(chǎn)生可以不由零開始,直接從一個(gè)已經(jīng)具備一定雛形的對(duì)象克隆,然后再修改為一個(gè)生產(chǎn)需要的對(duì)象。也就是說(shuō),產(chǎn)生一個(gè)人,可以不從1 歲 長(zhǎng)到2 歲,再3 歲…,也可以直接找一個(gè)人,從其身上獲得DNS,然后克隆一個(gè),直接修改一下就是3 歲的了!,我們講的原型模式也就是這樣的功能,是緊跟時(shí)代潮流的。


發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 息烽县| 化州市| 巧家县| 南昌市| 烟台市| 承德县| 鲁甸县| 威远县| 海南省| 中卫市| 蒙城县| 孙吴县| 叙永县| 天津市| 阿坝县| 甘谷县| 临夏市| 庄浪县| 阜阳市| 汪清县| 乌苏市| 鄯善县| 咸丰县| 沅江市| 诸城市| 荆州市| 图木舒克市| 公安县| 包头市| 两当县| 昌乐县| 徐闻县| 自治县| 丰原市| 龙里县| 南乐县| 焦作市| 东明县| 元阳县| 东宁县| 普洱|