最近在做一個小項目,定時計算一些金融指標。在系統運行過程中,可能會由於資料等原因出現不同的錯誤。但由於系統會在伺服器上7*24小時運行,出現一些無關大局的錯誤不該影響系統計算其他指標,但必須把錯誤記錄下來。
其實這非常容易實現,只要在出現錯誤的地方調用寫入日誌的函數即可。
但問題是,當出現錯誤時,錯誤記錄檔不一定被寫到檔案中,或許會被輸出到介面上的一個ListView中,甚至通過網路發送。
比如
Code
if(error)
{
WriteLogToTxtFile();
WriteLogToListView();
SendLogToNet();
}
當然,這三種方式都只是假設,並且如果再增加新的方式,我可能要在所有出錯的地方都增加新的函數調用。
你可能會說,寫一個函數叫WriteLog(),在裡面封裝各個寫日誌的函數
像這樣
1if(error)
2{
3 WriteLog(string log);
4}
5
6void WriteLog(string log)
7{
8 WriteLogToTxtFile(log);
9 WriteLogToListView(log);
10 //當增加新的寫日誌方式是,在這裡添加
11}
這當然可以,但不夠優雅。或許這馬上讓你想起了什麼!對,觀察者模式。
觀察者模式--
定義對象間一種一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並被自動更新。
其實觀察者模式大多數情況下是用來處理UI操作的。比如一個按鈕被點擊時,N個觀察者做出反應。但在這個例子中,被觀察對象不再是UI控制項,而是系統輸出的日誌。
1Code
2//日誌結構體
3struct defLogNode
4{
5 defLogNode(COleDateTime tmDate, string szText, string szDescription)
6 :m_tmDate(tmDate),
7 m_szText(szText),
8 m_szDescription(szDescription)
9 {
10
11 }
12 COleDateTime m_tmDate;
13 string m_szText;
14 string m_szDescription;
15};
16
17//日誌觀察者基類
18class CLogObserverBase
19{
20public:
21 virtual void InsertErrorLog(defLogNode &log) = 0;
22};
23
24//寫入檔案的觀察者
25class CLogToFile : public CLogObserverBase
26{
27public:
28 CLogToFile(){
29 m_pFile = NULL;
30 }
31 void InsertErrorLog(defLogNode & log);
32
33private:
34 FILE *m_pFile;
35
36 BOOL OpenLogFile();
37 BOOL WriteLogFile(string szLog);
38 BOOL CloseLogFile();
39};
40//在調試中寫到VC輸出視窗裡面
41class CLogToDebugOutput : public CLogObserverBase
42{
43 void InsertErrorLog(defLogNode & log);
44};
45
46
47class CLogAdmin
48{
49public:
50 static copiable_ptr<CLogAdmin> GetInstance();
51//這裡使用單件模式,當需要不同種類的日誌時,可以做些小的改動,用一個map管理多個SingleTon
52
53 void AddErrorObserver(CLogObserverBase * pObserver);
54
55 void InsertOneError(defLogNode & log);
56
57private:
58 CLogAdmin(){};
59 vector<copiable_ptr<CLogObserverBase> > m_vErrorObservers;
60
61 static copiable_ptr<CLogAdmin> s_Instance;
62//copiable_ptr是我自己寫的一個引用計數的靈巧指標。如需要請留言
63};
這樣,在系統初始化時,只需要
1 CLogAdminPtr log = CLogAdmin::GetInstance();
2 log->AddErrorObserver(new CLogToFile());
3 log->AddErrorObserver(new CLogToDebugOutput());
4
5//當增加一種輸出方式時,只需要派生一個新的子類,並在上面代碼之後加入一行
如果沒接觸過觀察者模式,其實這個問題也很好理解。我們在設計物件導向程式時,總是遵循一定原則的。
首先,依賴倒置,低層結構要依賴於高層結構。在這裡,調用錯誤記錄檔的地方就是高層結構,而具體的方法則是低層結構。如果想文章一開始那樣,則是讓高層結構依賴於低層結構了,程式便出現了偶合,因為在每個地方都要知道所有寫入日誌的方法。
其次,單一職責,當需要記錄日誌的地方必須知道有哪些寫入日誌的方法時,便使其職責不再單一。
第三,開放封閉,開放--對擴充開放;封閉--對修改封閉。這裡的擴充,指的就是擴充具體寫日誌的方法。修改,就是當增加一種寫日誌方法時,不該修改系統已有的程式。