一. 概述
Observer 模式要解決的問題為:建立一個一(Subject)對多(Observer)的依賴關系,並且做到當“一”變化的時候,依賴這個“一”的多也能夠同步改變。
Sbuject 相當於通知者,它提供依賴於它的觀察者Observer 的注冊(Attach)和注銷(Detach)操作,並且提供了使得依賴於它的所有觀察者同步的操作(Notify)。
Observer 相當於觀察者,則提供一個Update操作,注意這裡的 Observer 的 Update 操作並不在Observer 改變了Subject目標狀態的時候就對自己進行更新,這個更新操作要延遲到 Subject 對象發出 Notify 通知所有 Observer 進行修改(調用Update)。
二. 舉例
最常見的一個例子就是:對同一組數據進行統計分析時候,我們希望能夠提供多種形式的表示(例如以表格進行統計顯示、柱狀圖統計顯示、百分比統計顯示等)。這些表示都依賴於同一組數據,我們當然需要當數據改變的時候,所有的統計的顯示都能夠同時改變。
結構關系圖如下:
DataSubject : 我們就認為是原始數據。
SheetObserver:就認為是表格,用來顯示原始數據用的。
ChartObserver :就認為是圖表,也是來顯示原始數據的。
代碼如下:
[cpp] //////////////////////////////////////////////////////////////////////////
//觀察者基類
class Observer
{
public:
virtual ~Observer()
{
}
virtual void Update(Subject* sub) = 0;
virtual void PrintInfo() = 0;
protected:
Observer()
{
_st = '\0';
}
string _st;
};
//////////////////////////////////////////////////////////////////////////
//通知者基類
class Subject
{
public:
virtual ~Subject()
{
}
//注冊觀察者,這樣通知者就能通知到觀察者
virtual void Attach(Observer* obv)
{
_obvs->push_front(obv);
}
//注銷觀察者,通知者不再通知觀察者
virtual void Detach(Observer* obv)
{
if (obv != NULL)
_obvs->remove(obv);
}
//通知操作,通知後對於每個注冊過的觀察者,將會調用自己的update方法
virtual void Notify()
{
list<Observer*>::iterator it;
it = _obvs->begin();
for (;it != _obvs->end();it++)
{
(*it)->Update(this);
}
}
virtual void SetState(const string& st) = 0;
virtual string GetState() = 0;
protected:
Subject()
{
_obvs = new list<Observer*>;
}
private:
list<Observer* >* _obvs;
};
//////////////////////////////////////////////////////////////////////////
//具體的數據通知者
class DataSubject:public Subject
{
public:
DataSubject()
{
_st = '\0';
}
~DataSubject()
{
}
//自己的狀態
string GetState()
{
return _st;
}
void SetState(const string& st)
{
_st = st;
}
private:
string _st;
};
//////////////////////////////////////////////////////////////////////////
//數據表格觀察者
class SheetObserver:public Observer
{
public:
virtual Subject* GetSubject()
{
return _sub;
}
//構造函數裡,把自己注冊到通知者裡
SheetObserver(Subject* sub)
{
_sub = sub;
_sub->Attach(this);
}
virtual ~SheetObserver()
{
_sub->Detach(this);
if (_sub != 0)
delete _sub;
}
//更新操作
void Update(Subject* sub)
{
_st = sub->GetState(); //具體的數據可以從Subject這個通知者中取
PrintInfo();
}
void PrintInfo()
{
cout<<"Sheet observer.... "<<_sub->GetState()<<endl;
}
private:
Subject* _sub;
};
//數據圖表觀察者
class ChatObserver:public Observer
{
public:
virtual Subject* GetSubject()
{
return _sub;
}
ChatObserver(Subject* sub)
{
_sub = sub;
_sub->Attach(this);
}
virtual ~ChatObserver()
{
_sub->Detach(this);
if (_sub != 0)
{
delete _sub;
}
}
//更新操作
void Update(Subject* sub)
{
_st = sub->GetState();
PrintInfo();
}
void PrintInfo()
{
cout<<"Chat observer.... "<<_sub->GetState()<<endl;
}
private:
Subject* _sub;
};
//////////////////////////////////////////////////////////////////////////
//測試
int main()
{
DataSubject* sub = new DataSubject();//數據通知者
Observer* o1 = new SheetObserver(sub);//表格觀察者
Observer* o2 = new ChatObserver(sub);//圖表觀察者
sub->SetState("old data");//數據發生變化
sub->Notify();//通知者下發通知
sub->SetState("new data");
sub->Notify();
o1->Update(sub); //也可以由觀察者自己調用更新函數
return 0;
}
//////////////////////////////////////////////////////////////////////////
//觀察者基類
class Observer
{
public:
virtual ~Observer()
{
}
virtual void Update(Subject* sub) = 0;
virtual void PrintInfo() = 0;
protected:
Observer()
{
_st = '\0';
}
string _st;
};
//////////////////////////////////////////////////////////////////////////
//通知者基類
class Subject
{
public:
virtual ~Subject()
{
}
//注冊觀察者,這樣通知者就能通知到觀察者
virtual void Attach(Observer* obv)
{
_obvs->push_front(obv);
}
//注銷觀察者,通知者不再通知觀察者
virtual void Detach(Observer* obv)
{
if (obv != NULL)
_obvs->remove(obv);
}
//通知操作,通知後對於每個注冊過的觀察者,將會調用自己的update方法
virtual void Notify()
{
list<Observer*>::iterator it;
it = _obvs->begin();
for (;it != _obvs->end();it++)
{
(*it)->Update(this);
}
}
virtual void SetState(const string& st) = 0;
virtual string GetState() = 0;
protected:
Subject()
{
_obvs = new list<Observer*>;
}
private:
list<Observer* >* _obvs;
};
//////////////////////////////////////////////////////////////////////////
//具體的數據通知者
class DataSubject:public Subject
{
public:
DataSubject()
{
_st = '\0';
}
~DataSubject()
{
}
//自己的狀態
string GetState()
{
return _st;
}
void SetState(const string& st)
{
_st = st;
}
private:
string _st;
};
//////////////////////////////////////////////////////////////////////////
//數據表格觀察者
class SheetObserver:public Observer
{
public:
virtual Subject* GetSubject()
{
return _sub;
}
//構造函數裡,把自己注冊到通知者裡
SheetObserver(Subject* sub)
{
_sub = sub;
_sub->Attach(this);
}
virtual ~SheetObserver()
{
_sub->Detach(this);
if (_sub != 0)
delete _sub;
}
//更新操作 www.2cto.com
void Update(Subject* sub)
{
_st = sub->GetState(); //具體的數據可以從Subject這個通知者中取
PrintInfo();
}
void PrintInfo()
{
cout<<"Sheet observer.... "<<_sub->GetState()<<endl;
}
private:
Subject* _sub;
};
//數據圖表觀察者
class ChatObserver:public Observer
{
public:
virtual Subject* GetSubject()
{
return _sub;
}
ChatObserver(Subject* sub)
{
_sub = sub;
_sub->Attach(this);
}
virtual ~ChatObserver()
{
_sub->Detach(this);
if (_sub != 0)
{
delete _sub;
}
}
//更新操作
void Update(Subject* sub)
{
_st = sub->GetState();
PrintInfo();
}
void PrintInfo()
{
cout<<"Chat observer.... "<<_sub->GetState()<<endl;
}
private:
Subject* _sub;
};
//////////////////////////////////////////////////////////////////////////
//測試
int main()
{
DataSubject* sub = new DataSubject();//數據通知者
Observer* o1 = new SheetObserver(sub);//表格觀察者
Observer* o2 = new ChatObserver(sub);//圖表觀察者
sub->SetState("old data");//數據發生變化
sub->Notify();//通知者下發通知
sub->SetState("new data");
sub->Notify();
o1->Update(sub); //也可以由觀察者自己調用更新函數
return 0;
}說明:
1. 在 Observer 模式的實現中,Subject 維護一個 list 作為存儲其所有觀察者的容器。每當調用 Notify 操作就遍歷 list中的 Observer 對象,並廣播通知改變狀態(調用Observer的Update操作)。
2. 運行示例程序,可以看到當原始數據 Subject 處於狀態 “old” 時候,依賴於它的兩個觀察者都顯示 “old”,當原始數據狀態改變為 “new” 的時候,依賴於它的兩個觀察者也都改變為“new”。
3. 可以看到 Observer 與 Subject 互為耦合,但是這種耦合的雙方都依賴於抽象,而不依賴於具體。
三. MFC中的觀察者模式
MFC 的 View/Document 結構的實現中也采用了觀察者模式。
Document 為模式中的通知者,管理應用程序中的數據,View為模式中的觀察者,以給定的方顯示所關聯的 Document中的數據。CDocument類中定義了一個指針列表,用於保存對應的 CView 對象,並定義了一個函數用於對鏈表中的所有CView的對象進行更新。
結構如下:
原代碼如下:
[cpp] //afxwin.h
class CDocument : public CCmdTarget
{
public:
// Operations
void AddView(CView* pView); //注冊操作
void RemoveView(CView* pView); //注銷操作
// Update Views (simple update - DAG only) //通知操作
void UpdateAllViews(CView* pSender, LPARAM lHint = 0L,
CObject* pHint = NULL);
protected:
CPtrList m_viewList; // list of views
}
//DocCore.cpp
void CDocument::AddView(CView* pView)
{
ASSERT_VALID(pView);
ASSERT(pView->m_pDocument == NULL); // must not be already attached
ASSERT(m_viewList.Find(pView, NULL) == NULL); // must not be in list
m_viewList.AddTail(pView); //加入鏈表中
ASSERT(pView->m_pDocument == NULL); // must be un-attached
pView->m_pDocument = this;
OnChangedViewList(); // must be the last thing done to the document
}
void CDocument::RemoveView(CView* pView)
{
ASSERT_VALID(pView);
ASSERT(pView->m_pDocument == this); // must be attached to us
m_viewList.RemoveAt(m_viewList.Find(pView)); //從鏈表中刪除
pView->m_pDocument = NULL;
OnChangedViewList(); // must be the last thing done to the document
}
void CDocument::UpdateAllViews(CView* pSender, LPARAM lHint, CObject* pHint)
// walk through all views
{
ASSERT(pSender == NULL || !m_viewList.IsEmpty());
// must have views if sent by one of them
POSITION pos = GetFirstViewPosition(); //遍歷所有觀察者
while (pos != NULL)
{
CView* pView = GetNextView(pos);
ASSERT_VALID(pView);
if (pView != pSender)
pView->OnUpdate(pSender, lHint, pHint);
}
}
//afxwin.h
class CDocument : public CCmdTarget
{
public:
// Operations
void AddView(CView* pView); //注冊操作
void RemoveView(CView* pView); //注銷操作
// Update Views (simple update - DAG only) //通知操作
void UpdateAllViews(CView* pSender, LPARAM lHint = 0L,
CObject* pHint = NULL);
protected:
CPtrList m_viewList; // list of views
}
//DocCore.cpp
void CDocument::AddView(CView* pView)
{
ASSERT_VALID(pView);
ASSERT(pView->m_pDocument == NULL); // must not be already attached
ASSERT(m_viewList.Find(pView, NULL) == NULL); // must not be in list
m_viewList.AddTail(pView); //加入鏈表中
ASSERT(pView->m_pDocument == NULL); // must be un-attached
pView->m_pDocument = this;
OnChangedViewList(); // must be the last thing done to the document
}
void CDocument::RemoveView(CView* pView)
{
ASSERT_VALID(pView);
ASSERT(pView->m_pDocument == this); // must be attached to us
m_viewList.RemoveAt(m_viewList.Find(pView)); //從鏈表中刪除
pView->m_pDocument = NULL;
OnChangedViewList(); // must be the last thing done to the document
}
void CDocument::UpdateAllViews(CView* pSender, LPARAM lHint, CObject* pHint)
// walk through all views
{
ASSERT(pSender == NULL || !m_viewList.IsEmpty());
// must have views if sent by one of them
POSITION pos = GetFirstViewPosition(); //遍歷所有觀察者
while (pos != NULL)
{
CView* pView = GetNextView(pos);
ASSERT_VALID(pView);
if (pView != pSender)
pView->OnUpdate(pSender, lHint, pHint);
}
}從代碼中我們可以看到,AddView 和 RemoveView 相當於注冊和注銷操作,UpdateAllViews 相當於通知操作,通知操作會依次調用各個CView 對象的 OnUpdate,進行更新。
作者 lwbeyond