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

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 原型模式,即指定類(lèi)實(shí)現(xiàn)Cloneable接口,覆寫(xiě)clone方法。在創(chuàng)建對(duì)象時(shí),先new產(chǎn)生一個(gè)對(duì)象,其余對(duì)象均由...
    悠然望劍閱讀 274評(píng)論 0 0
  • 原型模式定義:用原型實(shí)例指定創(chuàng)建對(duì)象的種類(lèi), 并且通過(guò)拷貝這些原型創(chuàng)建新的對(duì)象。 原型模式的核心是一個(gè)clone方...
    代碼墨白閱讀 108評(píng)論 0 0
  • 定義 用原型實(shí)例指定創(chuàng)建對(duì)象的種類(lèi),并通過(guò)拷貝這些原型創(chuàng)建新的對(duì)象 使用場(chǎng)景 (1)類(lèi)初始化需要消耗非常多的資源,...
    某翼閱讀 283評(píng)論 0 1
  • 概念 在Java中實(shí)現(xiàn)原型模式十分簡(jiǎn)單,只需要實(shí)現(xiàn)Cloneable接口并重寫(xiě)clone()方法就可以了 Code...
    tanoak閱讀 684評(píng)論 0 0
  • 定義:用原型實(shí)例指定創(chuàng)建對(duì)象的種類(lèi),并通過(guò)拷貝這些原型創(chuàng)建新的對(duì)象。 類(lèi)型:創(chuàng)建類(lèi)模式 類(lèi)圖: 原型模式主要用于對(duì)...
    郭某人1閱讀 251評(píng)論 0 0

友情鏈接更多精彩內(nèi)容