銀行發(fā)廣告信,為了提供個(gè)性化服務(wù),發(fā)過(guò)去的郵件需要帶上個(gè)人信息,如XX先生/小姐,又或者是電子賬單,這就需要一個(gè)模板,再由具體數(shù)據(jù)填充成一份完整的郵件。

AdvTemplate 是廣告信的模板,一般都是從數(shù)據(jù)庫(kù)取出,生成一個(gè) BO 或者是 DTO,我們這里使用一個(gè)靜態(tài)的值來(lái)做代表;
public class AdvTemplate {
????//廣告信名稱(chēng)
????private String advSubject ="XX銀行國(guó)慶信用卡抽獎(jiǎng)活動(dòng)";
????//廣告信內(nèi)容
????private String advContext = "國(guó)慶抽獎(jiǎng)活動(dòng)通知:只要刷卡就送你1百萬(wàn)!....";
????//取得廣告信的名稱(chēng)
????public String getAdvSubject(){
????????return this.advSubject;
????}
????//取得廣告信的內(nèi)容
????public String getAdvContext(){
????????return this.advContext;
????}
}
Mail 類(lèi)是一封郵件類(lèi),發(fā)送機(jī)發(fā)送的就是這個(gè)類(lèi)。
public class Mail {
????//收件人
????private String receiver;
????//郵件名稱(chēng)
????private String subject;
????//稱(chēng)謂
????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;
????}
}
業(yè)務(wù)場(chǎng)景類(lèi)
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();
? ? }
}
現(xiàn)在發(fā)送郵件是單線程的,假設(shè)每封郵件0.02秒,600 萬(wàn)封郵件需要33小時(shí),今天發(fā)送不完畢,明天的賬單又產(chǎn)生了,積累積累,激起甲方人員一堆抱怨。
把 sendMail 修改為多線程,但是你只把 sendMail 修改為多線程還是有問(wèn)題的呀,你看哦,產(chǎn)生第一封郵件對(duì)象,放到線程 1 中運(yùn)行,還沒(méi)有發(fā)送出去;線程 2 呢也也啟動(dòng)了,直接就把郵件對(duì)象 mail的收件人地址和稱(chēng)謂修改掉了,線程不安全了,好了,說(shuō)到這里,你會(huì)說(shuō)這有 N 多種解決辦法,我們不多說(shuō),我們今天就說(shuō)一種,使用原型模式來(lái)解決這個(gè)問(wèn)題,使用對(duì)象的拷貝功能來(lái)解決這個(gè)問(wèn)題,類(lèi)圖稍作修改:

增加了一個(gè) Cloneable 接口, Mail 實(shí)現(xiàn)了這個(gè)接口,在 Mail 類(lèi)中重寫(xiě)了 clone()方法,我們來(lái)看 Mail類(lèi)的改變:
public class Mail implements Cloneable{
????//收件人
????private String receiver;
????//郵件名稱(chēng)
????private String subject;
????//稱(chēng)謂
????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方法
}
看 Client 類(lèi)的改變:
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è)置稱(chēng)謂,設(shè)置收件人地址等等。這種不通過(guò) new 關(guān)鍵字來(lái)產(chǎn)生一個(gè)對(duì)象,而是通過(guò)對(duì)象拷貝來(lái)實(shí)現(xiàn)的模式就叫做原型模式,其通用類(lèi)圖如下

這個(gè)模式的核心是一個(gè) clone 方法,通過(guò)這個(gè)方法進(jìn)行對(duì)象的拷貝,Java 提供了一個(gè) Cloneable 接口來(lái)標(biāo)示這個(gè)對(duì)象是可拷貝的,為什么說(shuō)是“標(biāo)示”呢?翻開(kāi) JDK 的幫助看看 Cloneable 是一個(gè)方法都沒(méi)有的,這個(gè)接口只是一個(gè)標(biāo)記作用,在 JVM 中具有這個(gè)標(biāo)記的對(duì)象才有可能被拷貝,那怎么才能從“有可能被拷貝”轉(zhuǎn)換為“可以被拷貝”呢?方法是覆蓋 clone()方法。
在 clone()方法上增加了一個(gè)注解@Override,沒(méi)有繼承一個(gè)類(lèi)為什么可以重寫(xiě)呢?在 Java 中所有類(lèi)的老祖宗是誰(shuí)?Object 類(lèi),每個(gè)類(lèi)默認(rèn)都是繼承了這個(gè)類(lèi),所以這個(gè)用上重寫(xiě)是非常正確的。
在 Java 中使用原型模式也就是 clone 方法還是有一些注意事項(xiàng)的
1.對(duì)象拷貝時(shí),類(lèi)的構(gòu)造函數(shù)是不會(huì)被執(zhí)行的。對(duì)象拷貝時(shí)確實(shí)構(gòu)造函數(shù)沒(méi)有被執(zhí)行,這個(gè)從原理來(lái)講也是可以講得通的,Object 類(lèi)的 clone 方法的原理是從內(nèi)存中(具體的說(shuō)就是堆內(nèi)存)以二進(jìn)制流的方式進(jìn)行拷貝,重新分配一個(gè)內(nèi)存塊,那構(gòu)造函數(shù)沒(méi)有被執(zhí)行也是非常正常的了。
2.淺拷貝和深拷貝問(wèn)題。
淺拷貝
public class Thing implements Cloneable{
????//定義一個(gè)私有變量
????private ArrayListarrayList = new ArrayList();
????@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 getValue(){return this.arrayList;}
}
在 Thing 類(lèi)中增加一個(gè)私有變量 arrayLis,類(lèi)型為 ArrayList,然后通過(guò) setValue 和 getValue 分別進(jìn)行設(shè)置和取值,我們來(lái)看場(chǎng)景類(lèi):
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é)果:
[張三, 李四]?
怎么會(huì)有李四呢?是因?yàn)?Java 做了一個(gè)偷懶的拷貝動(dòng)作,Object 類(lèi)提供的方法 clone 只是拷貝本對(duì)象,其對(duì)象內(nèi)部的數(shù)組、引用對(duì)象等都不拷貝,還是指向原生對(duì)象的內(nèi)部元素地址,這種拷貝就叫做淺拷貝,兩個(gè)對(duì)象共享了一個(gè)私有變量,你改我改大家都能改,是一個(gè)種非常不安全的方式。
深拷貝:
public class Thing implements Cloneable{
????//定義一個(gè)私有變量
????private ArrayListarrayList = new ArrayList();
????@Overridepublic Thing clone(){?
????????Thing thing=null;
?????????try {?
????????????????thing = (Thing)super.clone();
? ? ? ? ? ? ? ? thing.arrayList = (ArrayList)this.arrayList.clone();
? ? ? ? ?} catch (CloneNotSupportedException e) {
????????????????e.printStackTrace();
????????}
????????return thing;
????}
}
運(yùn)行結(jié)果如下:
[張三]
這個(gè)實(shí)現(xiàn)了完全的拷貝,兩個(gè)對(duì)象之間沒(méi)有任何的瓜葛了
3.Clone 與 final 兩對(duì)冤家,對(duì)象的 clone 與對(duì)象內(nèi)的 final 屬性是由沖突的。
public class Thing implements Cloneable{
????//定義一個(gè)私有變量
????private final ArrayListarrayList = new ArrayList();
????@Override
????public Thing clone(){?
????????Thing thing=null;?
????????try {
????????????thing = (Thing)super.clone();?
????????????this.arrayList = (ArrayList)this.arrayList.clone();
????????} catch (CloneNotSupportedException e) {
????????????e.printStackTrace();
????????}
????????return thing;
????}
}
ArrayListarrayList?增加了一個(gè) final 關(guān)鍵字,你要使用 clone 方法就在類(lèi)的成員變量上不要增加 final 關(guān)鍵字。