銀行發(fā)廣告信砍濒,為了提供個性化服務(wù),發(fā)過去的郵件需要帶上個人信息叫潦,如XX先生/小姐蝇完,又或者是電子賬單,這就需要一個模板矗蕊,再由具體數(shù)據(jù)填充成一份完整的郵件短蜕。
AdvTemplate 是廣告信的模板,一般都是從數(shù)據(jù)庫取出傻咖,生成一個 BO 或者是 DTO朋魔,我們這里使用一個靜態(tài)的值來做代表;
public class AdvTemplate {
????//廣告信名稱
????private String advSubject ="XX銀行國慶信用卡抽獎活動";
????//廣告信內(nèi)容
????private String advContext = "國慶抽獎活動通知:只要刷卡就送你1百萬卿操!....";
????//取得廣告信的名稱
????public String getAdvSubject(){
????????return this.advSubject;
????}
????//取得廣告信的內(nèi)容
????public String getAdvContext(){
????????return this.advContext;
????}
}
Mail 類是一封郵件類警检,發(fā)送機(jī)發(fā)送的就是這個類。
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;
????}
}
業(yè)務(wù)場景類
public class Client {
????//發(fā)送賬單的數(shù)量扇雕,這個值是從數(shù)據(jù)庫中獲得
????private static int MAX_COUNT = 6;
????public static void main(String[] args) {
????????//模擬發(fā)送郵件
????????int i=0;
????????//把模板定義出來,這個是從數(shù)據(jù)庫中獲得
????????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ā)送成功窥摄!");
? ? }
? ??//獲得指定長度的隨機(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 萬封郵件需要33小時溪王,今天發(fā)送不完畢,明天的賬單又產(chǎn)生了值骇,積累積累莹菱,激起甲方人員一堆抱怨。
把 sendMail 修改為多線程吱瘩,但是你只把 sendMail 修改為多線程還是有問題的呀道伟,你看哦,產(chǎn)生第一封郵件對象,放到線程 1 中運(yùn)行蜜徽,還沒有發(fā)送出去祝懂;線程 2 呢也也啟動了,直接就把郵件對象 mail的收件人地址和稱謂修改掉了拘鞋,線程不安全了砚蓬,好了,說到這里盆色,你會說這有 N 多種解決辦法灰蛙,我們不多說,我們今天就說一種隔躲,使用原型模式來解決這個問題摩梧,使用對象的拷貝功能來解決這個問題,類圖稍作修改:
增加了一個 Cloneable 接口宣旱, Mail 實(shí)現(xiàn)了這個接口仅父,在 Mail 類中重寫了 clone()方法,我們來看 Mail類的改變:
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方法
}
看 Client 類的改變:
public class Client {
????//發(fā)送賬單的數(shù)量笙纤,這個值是從數(shù)據(jù)庫中獲得
????private static int MAX_COUNT = 6;
????public static void main(String[] args) {
????????//模擬發(fā)送郵件
????????int i=0;
????????//把模板定義出來,這個是從數(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 即使是多線程也沒有關(guān)系,看到mail.clone()這個方法了嗎忿项?把對象拷貝一份蓉冈,產(chǎn)生一個新的對象,和原有對象一樣轩触,然后再修改細(xì)節(jié)的數(shù)據(jù)寞酿,如設(shè)置稱謂,設(shè)置收件人地址等等脱柱。這種不通過 new 關(guān)鍵字來產(chǎn)生一個對象伐弹,而是通過對象拷貝來實(shí)現(xiàn)的模式就叫做原型模式,其通用類圖如下
這個模式的核心是一個 clone 方法榨为,通過這個方法進(jìn)行對象的拷貝惨好,Java 提供了一個 Cloneable 接口來標(biāo)示這個對象是可拷貝的,為什么說是“標(biāo)示”呢随闺?翻開 JDK 的幫助看看 Cloneable 是一個方法都沒有的日川,這個接口只是一個標(biāo)記作用,在 JVM 中具有這個標(biāo)記的對象才有可能被拷貝矩乐,那怎么才能從“有可能被拷貝”轉(zhuǎn)換為“可以被拷貝”呢龄句?方法是覆蓋 clone()方法回论。
在 clone()方法上增加了一個注解@Override,沒有繼承一個類為什么可以重寫呢分歇?在 Java 中所有類的老祖宗是誰傀蓉?Object 類,每個類默認(rèn)都是繼承了這個類职抡,所以這個用上重寫是非常正確的葬燎。
在 Java 中使用原型模式也就是 clone 方法還是有一些注意事項(xiàng)的
1.對象拷貝時,類的構(gòu)造函數(shù)是不會被執(zhí)行的繁调。對象拷貝時確實(shí)構(gòu)造函數(shù)沒有被執(zhí)行萨蚕,這個從原理來講也是可以講得通的,Object 類的 clone 方法的原理是從內(nèi)存中(具體的說就是堆內(nèi)存)以二進(jìn)制流的方式進(jìn)行拷貝蹄胰,重新分配一個內(nèi)存塊岳遥,那構(gòu)造函數(shù)沒有被執(zhí)行也是非常正常的了。
2.淺拷貝和深拷貝問題裕寨。
淺拷貝
public class Thing implements Cloneable{
????//定義一個私有變量
????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 類中增加一個私有變量 arrayLis浩蓉,類型為 ArrayList,然后通過 setValue 和 getValue 分別進(jìn)行設(shè)置和取值,我們來看場景類:
public class Client {
????public static void main(String[] args) {
????????//產(chǎn)生一個對象
????????Thing thing = new Thing();
????????//設(shè)置一個值
????????thing.setValue("張三");
????????//拷貝一個對象
????????Thing cloneThing = thing.clone();
????????cloneThing.setValue("李四");
????????System.out.println(thing.getValue());
????}
}
運(yùn)行結(jié)果:
[張三, 李四]?
怎么會有李四呢宾袜?是因?yàn)?Java 做了一個偷懶的拷貝動作捻艳,Object 類提供的方法 clone 只是拷貝本對象,其對象內(nèi)部的數(shù)組庆猫、引用對象等都不拷貝认轨,還是指向原生對象的內(nèi)部元素地址,這種拷貝就叫做淺拷貝月培,兩個對象共享了一個私有變量嘁字,你改我改大家都能改,是一個種非常不安全的方式杉畜。
深拷貝:
public class Thing implements Cloneable{
????//定義一個私有變量
????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é)果如下:
[張三]
這個實(shí)現(xiàn)了完全的拷貝纪蜒,兩個對象之間沒有任何的瓜葛了
3.Clone 與 final 兩對冤家,對象的 clone 與對象內(nèi)的 final 屬性是由沖突的此叠。
public class Thing implements Cloneable{
????//定義一個私有變量
????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?增加了一個 final 關(guān)鍵字纯续,你要使用 clone 方法就在類的成員變量上不要增加 final 關(guān)鍵字。