程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 設計模式C++描述----04.觀察者(Observer)模式

設計模式C++描述----04.觀察者(Observer)模式

編輯:C++入門知識

一. 概述

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved