設(shè)計模式之禪-原型模式

銀行發(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)鍵字。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末灭袁,一起剝皮案震驚了整個濱河市猬错,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌茸歧,老刑警劉巖兔魂,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異举娩,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進(jìn)店門铜涉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來智玻,“玉大人,你說我怎么就攤上這事芙代〉跎荩” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵纹烹,是天一觀的道長页滚。 經(jīng)常有香客問我,道長铺呵,這世上最難降的妖魔是什么裹驰? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮片挂,結(jié)果婚禮上幻林,老公的妹妹穿的比我還像新娘。我一直安慰自己音念,他們只是感情好沪饺,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著闷愤,像睡著了一般整葡。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上讥脐,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天遭居,我揣著相機(jī)與錄音,去河邊找鬼攘烛。 笑死魏滚,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的坟漱。 我是一名探鬼主播鼠次,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼芋齿!你這毒婦竟也來了腥寇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤觅捆,失蹤者是張志新(化名)和其女友劉穎赦役,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體栅炒,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掂摔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年术羔,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乙漓。...
    茶點(diǎn)故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡级历,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出叭披,到底是詐尸還是另有隱情寥殖,我是刑警寧澤,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布涩蜘,位于F島的核電站嚼贡,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏同诫。R本人自食惡果不足惜粤策,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望剩辟。 院中可真熱鬧掐场,春花似錦、人聲如沸贩猎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吭服。三九已至嚷堡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間艇棕,已是汗流浹背蝌戒。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留沼琉,地道東北人北苟。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像打瘪,于是被迫代替她去往敵國和親友鼻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評論 2 355

推薦閱讀更多精彩內(nèi)容