利用Observer(观察者)模式实现系统日志

最近在做一个小项目,定时计算一些金融指标。在系统运行过程中,可能会由于数据等原因出现不同的错误。但由于系统会在服务器上7*24小时运行,出现一些无关大局的错误不该影响系统计算其他指标,但必须把错误记录下来。

其实这非常容易实现,只要在出现错误的地方调用写入日志的函数即可。

但问题是,当出现错误时,错误日志不一定被写到文件中,或许会被输出到界面上的一个ListView中,甚至通过网络发送。

比如


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//当增加一种输出方式时,只需要派生一个新的子类,并在上面代码之后加入一行

 

如果没接触过观察者模式,其实这个问题也很好理解。我们在设计面向对象程序时,总是遵循一定原则的。

 

首先,依赖倒置,低层结构要依赖于高层结构。在这里,调用错误日志的地方就是高层结构,而具体的方法则是低层结构。如果想文章一开始那样,则是让高层结构依赖于低层结构了,程序便出现了偶合,因为在每个地方都要知道所有写入日志的方法。

其次,单一职责,当需要记录日志的地方必须知道有哪些写入日志的方法时,便使其职责不再单一。

第三,开放封闭,开放
--对扩展开放;封闭--对修改封闭。这里的扩展,指的就是扩展具体写日志的方法。修改,就是当增加一种写日志方法时,不该修改系统已有的程序。
posted @ 2008-10-03 21:52  刺儿头  阅读(709)  评论(0编辑  收藏  举报