第13章 原型模式
13.1 個性化電子賬單
現在電子賬單越來越流行了,比如你的信用卡,每到月初的時候銀行就會發一份電子郵件給你,說你這個月消費了多少,什么時候消費的,積分是多少等,這是每個月發一次。還有一種也是銀行發的郵件你肯定非常有印象:廣告信,現在各大銀行的信用卡部門都在拉攏客戶,電子郵件是一種廉價、快捷的通信方式,你用紙質的廣告信那個費用多高呀,比如我行今天推出一個信用卡刷卡抽獎活動,通過電子賬單系統可以一個晚上發送給600萬客戶,為什么要用電子賬單系統呢?直接找個發垃圾郵件的工具不就解決問題了嗎?是個好主意,但是這個方案在金融行業是行不通的,為什么?因為銀行發送該類郵件是有要求的:
● 個性化服務
一般銀行都要求個性化服務,發過去的郵件上總有一些個人信息吧,比如“××先生”,“××女士”等。
● 遞送成功率
郵件的遞送成功率有一定的要求,由于大批量地發送郵件會被接收方郵件服務器誤認是垃圾郵件,因此在郵件頭要增加一些偽造數據,以規避被反垃圾郵件引擎誤認為是垃圾郵件。
從這兩方面考慮廣告信的發送也是電子賬單系統(電子賬單系統一般包括:賬單分析、賬單生成器、廣告信管理、發送隊列管理、發送機、退信處理、報表管理等)的一個子功能,我們今天就來考慮一下廣告信這個模塊是怎么開發的。那既然是廣告信,肯定需要一個模板,然后再從數據庫中把客戶的信息一個一個地取出,放到模板中生成一份完整的郵件,然后扔給發送機進行發送處理,類圖如圖13-1所示。
在類圖中AdvTemplate是廣告信的模板,一般都是從數據庫取出,生成一個BO或者是DTO,我們這里使用一個靜態的值來作代表;Mail類是一封郵件類,發送機發送的就是這個類。我們先來看AdvTemplate,如代碼清單13-1所示。

圖13-1 發送電子賬單類圖
代碼清單13-1 廣告信模板代碼
public?class?AdvTemplate?{
?????//廣告信名稱
?????private?String?advSubject?="XX銀行國慶信用卡抽獎活動";
?????//廣告信內容
?????private?String?advContext?=?"國慶抽獎活動通知:只要刷卡就送你一百萬!...";
?????//取得廣告信的名稱
?????public?String?getAdvSubject(){
?????????????return?this.advSubject;
?????}
?????//取得廣告信的內容
?????public?String?getAdvContext(){
?????????????return?this.advContext;
?????}
}
郵件類Mail如代碼清單13-2所示。
代碼清單13-2 郵件類代碼
public?class?Mail?{
?????//收件人
?????private?String?receiver;
?????//郵件名稱
?????private?String?subject;
?????//稱謂
?????private?String?appellation;
?????//郵件內容
?????private?String?contxt;?????
?????//郵件的尾部,一般都是加上"XXX版權所有"等信息
?????private?String?tail;
?????//構造函數
?????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類就是一個業務對象,雖然比較長,還是比較簡單的。我們再來看業務場景類是如何對郵件繼續處理的,如代碼清單11-3所示。
代碼清單13-3 場景類
public?class?Client?{
?????//發送賬單的數量,這個值是從數據庫中獲得
?????private?static?int?MAX_COUNT?=?6;
?????public?static?void?main(String[]?args)?{
???????????????//模擬發送郵件
???????????????int?i=0;
???????????????//把模板定義出來,這個是從數據庫中獲得
???????????????Mail?mail?=?new?Mail(new?AdvTemplate());
???????????????mail.setTail("XX銀行版權所有");
???????????????while(i<MAX_COUNT){
???????????????????????//以下是每封郵件不同的地方
???????????????????????mail.setAppellation(getRandString(5)+"?先生(女士)");
???????????????????????mail.setReceiver(getRandString(5)+"@"+getRandString(8)?+".com");?
???????????????????????//然后發送郵件
???????????????????????sendMail(mail);
???????????????????????i++;
?????????????}
?????}??
?????//發送郵件
?????public?static?void?sendMail(Mail?mail){
?????????????System.out.println("標題:"+mail.getSubject()?+?"\t收件人:
????????????????"+mail.getReceiver()+"\t...發送成功!");
?????}??
?????//獲得指定長度的隨機字符串
?????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();
?????}
}
運行結果如下所示:
?
| 標題:XX銀行國慶信用卡抽獎活動 | 收件人:fjQUm@ZnkyPSsL.com | ...發送成功! |
|-----|-----|-----|
| 標題:XX銀行國慶信用卡抽獎活動 | 收件人:ZIKnC@NOKdloNM.com | ...發送成功! |
| 標題:XX銀行國慶信用卡抽獎活動 | 收件人:zNkMI@HpMMSZaz.com | ...發送成功! |
| 標題:XX銀行國慶信用卡抽獎活動 | 收件人:oMTFA@uBwkRjxa.com | ...發送成功! |
| 標題:XX銀行國慶信用卡抽獎活動 | 收件人:TquWT@TLLVNFja.com | ...發送成功! |
| 標題:XX銀行國慶信用卡抽獎活動 | 收件人:rkQbp@mfATHDQH.com | ...發送成功! |
由于是隨機數,每次運行都有所差異,不管怎么樣,我們這個電子賬單發送程序是編寫出來了,也能正常發送。我們再來仔細地想想,這個程序是否有問題?Look here,這是一個線程在運行,也就是你發送的是單線程的,那按照一封郵件發出去需要0.02秒(夠小了,你還要到數據庫中取數據呢),600萬封郵件需要33個小時,也就是一個整天都發送不完,今天的沒發送完,明天的賬單又產生了,日積月累,激起甲方人員一堆抱怨,那怎么辦?
好辦,把sendMail修改為多線程,但是只把sendMail修改為多線程還是有問題的呀,產生第一封郵件對象,放到線程1中運行,還沒有發送出去;線程2也啟動了,直接就把郵件對象mail的收件人地址和稱謂修改掉了,線程不安全了。說到這里,你會說這有N多種解決辦法,其中一種是使用一種新型模式來解決這個問題:通過對象的復制功能來解決這個問題,類圖稍做修改,如圖13-2所示。

圖13-2 修改后的發送電子賬單類圖
增加了一個Cloneable接口(Java自帶的一個接口), Mail實現了這個接口,在Mail類中覆寫clone()方法,我們來看Mail類的改變,如代碼清單13-4所示。
代碼清單13-4 修改后的郵件類
public?class?Mail?implements?Cloneable{????
?????//收件人
?????private?String?receiver;
?????//郵件名稱
?????private?String?subject;
?????//稱謂
?????private?String?appellation;
?????//郵件內容
?????private?String?contxt;?????
?????//郵件的尾部,一般都是加上"XXX版權所有"等信息
?????private?String?tail;
?????//構造函數
?????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;
?????}??
}
注意看粗體部分,實現了一個接口,并重寫了clone方法,大家可能看著這個類有點奇怪,先保留你的好奇,我們繼續講下去,稍后會給你清晰的答案。我們再來看場景Client的變化,如代碼清單13-5所示。
代碼清單13-5 修改后的場景類
public?class?Client?{
?????//發送賬單的數量,這個值是從數據庫中獲得
?????private?static?int?MAX_COUNT?=?6;
?????public?static?void?main(String[]?args)?{
?????????????//模擬發送郵件
?????????????int?i=0;
?????????????//把模板定義出來,這個是從數據中獲得
?????????????Mail?mail?=?new?Mail(new?AdvTemplate());
?????????????mail.setTail("XX銀行版權所有");
?????????????while(i<MAX_COUNT){
????????????????????//以下是每封郵件不同的地方
????????????????????Mail?cloneMail?=?mail.clone();
????????????????????cloneMail.setAppellation(getRandString(5)+"?先生(女士)");
????????????????????cloneMail.setReceiver(getRandString(5)+"@"+getRandString(8)+".com");
????????????????????//然后發送郵件
????????????????????sendMail(cloneMail);
????????????????????i++;
?????????????}
?????}
}
運行結果不變,一樣完成了電子廣告信的發送功能,而且sendMail即使是多線程也沒有關系。注意,看Client類中的粗體字mail.clone()這個方法,把對象復制一份,產生一個新的對象,和原有對象一樣,然后再修改細節的數據,如設置稱謂、設置收件人地址等。這種不通過new關鍵字來產生一個對象,而是通過對象復制來實現的模式就叫做原型模式。
- 前言
- 第一部分 大旗不揮,誰敢沖鋒——6大設計原則全新解讀
- 第1章 單一職責原則
- 1.2 絕殺技,打破你的傳統思維
- 1.3 我單純,所以我快樂
- 1.4 最佳實踐
- 第2章 里氏替換原則
- 2.2 糾紛不斷,規則壓制
- 2.3 最佳實踐
- 第3章 依賴倒置原則
- 3.2 言而無信,你太需要契約
- 3.3 依賴的三種寫法
- 3.4 最佳實踐
- 第4章 接口隔離原則
- 4.2 美女何其多,觀點各不同
- 4.3 保證接口的純潔性
- 4.4 最佳實踐
- 第5章 迪米特法則
- 5.2 我的知識你知道得越少越好
- 5.3 最佳實踐
- 第6章 開閉原則
- 6.2 開閉原則的廬山真面目
- 6.3 為什么要采用開閉原則
- 6.4 如何使用開閉原則
- 6.5 最佳實踐
- 第二部分 真刀實槍 ——23種設計模式完美演繹
- 第7章 單例模式
- 7.2 單例模式的定義
- 7.3 單例模式的應用
- 7.4 單例模式的擴展
- 7.5 最佳實踐
- 第8章 工廠方法模式
- 8.2 工廠方法模式的定義
- 8.3 工廠方法模式的應用
- 8.4 工廠方法模式的擴展
- 8.5 最佳實踐
- 第9章 抽象工廠模式
- 9.2 抽象工廠模式的定義
- 9.3 抽象工廠模式的應用
- 9.4 最佳實踐
- 第10章 模板方法模式
- 10.2 模板方法模式的定義
- 10.3 模板方法模式的應用
- 10.4 模板方法模式的擴展
- 10.5 最佳實踐
- 第11章 建造者模式
- 11.2 建造者模式的定義
- 11.3 建造者模式的應用
- 11.4 建造者模式的擴展
- 11.5 最佳實踐
- 第12章 代理模式
- 12.2 代理模式的定義
- 12.3 代理模式的應用
- 12.4 代理模式的擴展
- 12.5 最佳實踐
- 第13章 原型模式
- 13.2 原型模式的定義
- 13.3 原型模式的應用
- 13.4 原型模式的注意事項
- 13.5 最佳實踐
- 第14章 中介者模式
- 14.2 中介者模式的定義
- 14.3 中介者模式的應用
- 14.4 中介者模式的實際應用
- 14.5 最佳實踐
- 第15章 命令模式
- 15.2 命令模式的定義
- 15.3 命令模式的應用
- 15.4 命令模式的擴展
- 15.5 最佳實踐
- 第16章 責任鏈模式
- 16.2 責任鏈模式的定義
- 16.3 責任鏈模式的應用
- 16.4 最佳實踐
- 第17章 裝飾模式
- 17.2 裝飾模式的定義
- 17.3 裝飾模式應用
- 17.4 最佳實踐
- 第18章 策略模式
- 18.2 策略模式的定義
- 18.3 策略模式的應用
- 18.4 策略模式的擴展
- 18.5 最佳實踐
- 第19章 適配器模式
- 19.2 適配器模式的定義
- 19.3 適配器模式的應用
- 19.4 適配器模式的擴展
- 19.5 最佳實踐
- 第20章 迭代器模式
- 20.2 迭代器模式的定義
- 20.3 迭代器模式的應用
- 20.4 最佳實踐
- 第21章 組合模式
- 21.2 組合模式的定義
- 21.3 組合模式的應用
- 21.4 組合模式的擴展
- 21.5 最佳實踐
- 第22章 觀察者模式
- 22.2 觀察者模式的定義
- 22.3 觀察者模式的應用
- 22.4 觀察者模式的擴展
- 22.5 最佳實踐
- 第23章 門面模式
- 23.2 門面模式的定義
- 23.3 門面模式的應用
- 23.4 門面模式的注意事項
- 23.5 最佳實踐
- 第24章 備忘錄模式
- 24.2 備忘錄模式的定義
- 24.3 備忘錄模式的應用
- 24.4 備忘錄模式的擴展
- 24.5 最佳實踐
- 第25章 訪問者模式
- 25.2 訪問者模式的定義
- 25.3 訪問者模式的應用
- 25.4 訪問者模式的擴展
- 25.5 最佳實踐
- 第26章 狀態模式
- 26.2 狀態模式的定義
- 26.3 狀態模式的應用
- 第27章 解釋器模式
- 27.2 解釋器模式的定義
- 27.3 解釋器模式的應用
- 27.4 最佳實踐
- 第28章 享元模式
- 28.2 享元模式的定義
- 28.3 享元模式的應用
- 28.4 享元模式的擴展
- 28.5 最佳實踐
- 第29章 橋梁模式
- 29.2 橋梁模式的定義
- 29.3 橋梁模式的應用
- 29.4 最佳實踐
- 第三部分 誰的地盤誰做主 ——設計模式PK
- 第30章 創建類模式大PK
- 30.1 工廠方法模式VS建造者模式
- 30.2 抽象工廠模式VS建造者模式
- 第31章 結構類模式大PK
- 31.1 代理模式VS裝飾模式
- 31.2 裝飾模式VS適配器模式
- 第32章 行為類模式大PK
- 32.1 命令模式VS策略模式
- 32.2 策略模式VS狀態模式
- 32.3 觀察者模式VS責任鏈模式
- 第33章 跨戰區PK
- 33.1 策略模式VS橋梁模式
- 33.2 門面模式VS中介者模式
- 33.3 包裝模式群PK
- 第四部分 完美世界 ——設計模式混編
- 第34章 命令模式+責任鏈模式
- 34.2 混編小結
- 第35章 工廠方法模式+策略模式
- 35.2 混編小結
- 第36章 觀察者模式+中介者模式
- 36.2 混編小結
- 第五部分 擴展篇
- 第37章 MVC框架
- 37.2 最佳實踐
- 第38章 新模式
- 38.1 規格模式
- 38.2 對象池模式
- 38.3 雇工模式
- 38.4 黑板模式
- 38.5 空對象模式
- 附錄 23種設計模式彩圖