設計模式 c++版(9)——原型模式
定義:用原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件。
示例一:個性化電子賬單
1. 需求說明:
銀行傳送電子賬單的郵件一般是有要求的:①個性化服務:發過去的郵件上總有一些個人資訊,比如姓氏等。②遞送成功率:若大批量地傳送郵件會被收房郵件伺服器誤認為是垃圾郵件,因此在郵件頭要增加一些偽造資料,以規避被反垃圾郵件引擎誤認為是垃圾郵件
從這兩方面考慮,電子賬單系統(電子賬單系統一般包括:賬單分析、賬單生成器、廣告信管理、傳送佇列管理、傳送機、退信處理、報表管理等)的一個子功能,現在考慮一下廣告新這個模組如何開發。既然是廣告信,肯定需要一個模板,然後再從資料庫中把客戶的資訊一個一個地取出,放到模板中生成一份完整的郵件,然後扔給傳送機進行傳送處理
2. 個性化電子賬單,類圖13-1
3. 個性化電子賬單,類圖說明
AdvTemplate 是廣告信的模板,一般都是從資料庫取出,生成一個BO或是DTO,我們這裡使用一個驚天的值來代表 Mail 類是一封郵件類,傳送機傳送的就是這個類
4. 個性化電子賬單,程式碼清單13-1:
//////// ********** 1. 傳送電子賬單,程式碼清單13-1:***************// class AdvTemplate { public: AdvTemplate() { this->m_advSubject = "The National Day lottery"; this->m_advContext = "***********"; } QString getAdvSubject() { return this->m_advSubject; } QString getAdvContext() { return this->m_advContext; } private: QString m_advSubject; //title QString m_advContext; //text }; class Mail { public: Mail(){} Mail(AdvTemplate advTemplate) { this->m_contxt = advTemplate.getAdvContext(); this->m_subject = advTemplate.getAdvSubject(); } QString getReceiver() { return this->m_receiver; } void setReceiver(QString receiver) { this->m_receiver = receiver; } QString getSubject() { return this->m_subject; } void setSubject(QString subject) { this->m_subject = subject; } QString getAppellation() { return this->m_appellation; } void setAppellation(QString appellation) { this->m_appellation = appellation; } QString getContxt() { return this->m_contxt; } void setContxt(QString contxt) { this->m_contxt = contxt; } QString getTail() { return this->m_tail; } void setTail(QString tail) { this->m_tail = tail; } private: QString m_receiver; QString m_subject; QString m_appellation; QString m_contxt; QString m_tail; }; static int MAX_COUNT = 6; class Client { public: Client() { AdvTemplate temp; this->m_mail = Mail(temp); this->m_mail.setTail("copyright"); this->m_mail.setAppellation(getString(5)); this->m_mail.setReceiver(getString(5) + "@"); } void sendMail() { for (int i = 0; i < MAX_COUNT; ++i) { this->m_mail.setAppellation(getString(5)); this->m_mail.setReceiver(getString(5) + "@"); qDebug() << "title:" + m_mail.getSubject(); qDebug() << "receiver:" + m_mail.getReceiver(); } } static QString getString(int maxLength) { QString source = "abcdefghijklmn"; QString str = source.left(maxLength); return str; } private: Mail m_mail; }; int main() { Client client; client.sendMail(); return 0; }
5. 程式碼分析
這是一個執行緒在執行,也就是傳送的是單執行緒的,那按照一封郵件發出去需要0.02秒,600萬封郵件需要33個小時,也就是一整天都發送不完。在此做修改,把sendMail修改為多執行緒,但是隻把sendMail修改為多執行緒還是有問題,產生第一封郵件物件,放到執行緒1中執行,還沒有傳送出去;執行緒2也啟動了,直接把郵件物件mail的收件人地址和稱謂修改掉了,執行緒不安全了。這裡我們使用一種新型模式來解決這個問題:通過物件的複製功能來解決。
示例二:修改後的傳送電子賬單
1. 類圖,類圖13-2
2. 程式碼清單13-2:
//////// ********** 2. 修改後的傳送電子賬單,程式碼清單13-2:***************// class Mail; class Cloneable { public: virtual Mail clone() = 0; }; class AdvTemplate { public: AdvTemplate() { this->m_advSubject = "The National Day lottery"; this->m_advContext = "***********"; } QString getAdvSubject() { return this->m_advSubject; } QString getAdvContext() { return this->m_advContext; } private: QString m_advSubject; //title QString m_advContext; //text }; class Mail:public Cloneable { public: Mail(){} Mail(AdvTemplate advTemplate) { this->m_contxt = advTemplate.getAdvContext(); this->m_subject = advTemplate.getAdvSubject(); } virtual Mail clone() { Mail mail; mail.setAppellation(this->m_appellation); mail.setContxt(this->m_contxt); mail.setReceiver(this->m_receiver); mail.setSubject(this->m_receiver); mail.setTail(this->m_tail); return mail; } QString getReceiver() { return this->m_receiver; } void setReceiver(QString receiver) { this->m_receiver = receiver; } QString getSubject() { return this->m_subject; } void setSubject(QString subject) { this->m_subject = subject; } QString getAppellation() { return this->m_appellation; } void setAppellation(QString appellation) { this->m_appellation = appellation; } QString getContxt() { return this->m_contxt; } void setContxt(QString contxt) { this->m_contxt = contxt; } QString getTail() { return this->m_tail; } void setTail(QString tail) { this->m_tail = tail; } private: QString m_receiver; QString m_subject; QString m_appellation; QString m_contxt; QString m_tail; }; static int MAX_COUNT = 6; class Client { public: Client() { AdvTemplate temp; this->m_mail = Mail(temp); this->m_mail.setTail("copyright"); this->m_mail.setAppellation(getString(5)); this->m_mail.setReceiver(getString(5) + "@"); } void sendMail() { for (int i = 0; i < MAX_COUNT; ++i) { Mail mail = this->m_mail.clone(); mail.setAppellation(getString(5)); mail.setReceiver(getString(5) + "@"); qDebug() << "title:" + mail.getSubject(); qDebug() << "receiver:" + mail.getReceiver(); } } static QString getString(int maxLength) { QString source = "abcdefghijklmn"; QString str = source.left(maxLength); return str; } private: Mail m_mail; }; int main() { Client client; client.sendMail(); return 0; }
3.程式碼說明:
在這裡即使sendMail即使是多執行緒也沒有關係。在Client類中,mail.clone() 這個方法,把物件複製一份,產生一個新的物件,和原有物件一樣,然後再修改細節的資料。不通過new關鍵字來產生一個物件,而是通過物件複製來實現的模式叫做原型模式。
示例三:通用原型模式
1. 類圖13-3
2. 程式碼清單13-3:
class PrototypeClass;
class Cloneable
{
public:
virtual PrototypeClass clone() = 0;
};
class PrototypeClass:public Cloneable
{
public:
virtual PrototypeClass clone()
{
PrototypeClass proto;
//copy
return proto;
}
};
三、原型模式的應用
1. 優點:
效能優良。原型模式是在記憶體二進位制流的拷貝,比直接new一個物件效能好很多,特別死要在一個迴圈體內產生大量的物件時,原型模式可以更好地體現其優點 逃避建構函式的約束。直接在記憶體中拷貝,建構函式是不會執行的。
2. 使用場景:
資源優化場景。類初始化需要消化非常多的資源,這個資源包括資料、硬體資源等。 效能和安全要求的場景。通過 new 產生一個物件需要非常繁瑣的資料準備或訪問許可權,則可以使用原型模式。 一個物件多個修改者的場景。一個物件需要提供給其他物件訪問,而且各個呼叫者可能都需要修改其值時,可以考慮使用原型模式拷貝多個物件供呼叫者使用。
3. 注意事項:
物件拷貝時建構函式沒有被執行,Object類的clone 方法的原理是從記憶體中(具體地說就是堆記憶體)以二進位制流的方式進行拷貝,重新分配一個記憶體塊,那建構函式沒有被執行也很正常了。
四、最佳實踐
原型模式先產生出一個包含大量共有資訊的類,然後可以拷貝出副本,修正細節資訊,建立了一個完整的個性物件。
參考文獻《秦小波. 設計模式之禪》(第2版) (華章原創精品) 機械工業出版社