1. 程式人生 > >C++ 觀察者模式

C++ 觀察者模式

簡述

觀察者模式(Observer Pattern),定義了物件間的一對多的依賴關係,讓多個觀察者物件同時監聽某一個主題物件(被觀察者)。當主題物件的狀態發生更改時,會通知所有觀察者,讓它們能夠自動更新。

|

背景

很多時候,在應用程式的一部分發生更改時,需要同時更新應用程式的其他部分。有一種方法是:讓接收者反覆檢查傳送者來進行更新,但是這種方法存在兩個主要問題:

  • 佔用大量的 CPU 時間來檢查新的狀態
  • 依賴於檢測更新的時間間隔,可能不會立即獲得更新

對於這個問題,有一個簡單的解決方案 - 觀察者模式。

模式結構

UML 結構圖:

Observer Pattern

  • Subject(抽象主題):跟蹤所有觀察者,並提供新增和刪除觀察者的介面。
  • Observer(抽象觀察者):為所有的具體觀察者定義一個介面,在得到主題的通知時進行自我更新。
  • ConcreteSubject(具體主題):將有關狀態存入各 ConcreteObserver 物件。當具體主題的狀態發生任何更改時,通知所有觀察者。
  • ConcreteObserver(具體觀察者):實現 Observer 所要求的更新介面,以便使本身的狀態與主題的狀態相協調。

優缺點

優點:

  • 觀察者和被觀察者是抽象耦合的
  • 建立一套觸發機制

缺點:

  • 如果一個被觀察者物件有很多的直接和間接的觀察者,將所有的觀察者都通知到會花費很多時間。
  • 如果在觀察者和觀察目標之間有迴圈依賴的話,觀察目標會觸發它們之間進行迴圈呼叫,可能導致系統崩潰。
  • 觀察者模式沒有相應的機制讓觀察者知道所觀察的目標物件是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化。

適用場景

  • 有多個子類共有的方法,且邏輯相同。
  • 重要的、複雜的方法,可以考慮作為模板方法。

案例分析

滴滴一下,讓出行更美好

自從有了滴滴、快滴、Uber、神舟等各大打車平臺,廣大市民的出行便利了不少。但自從合併以後,補助少了,價格也上漲了很多,不 * XX 倍甚至打不到車。。。

滴滴:好,第一個月,價格上調至 12.5。。。
過了不久,心裡想著:納尼,都壟斷了,還不多漲漲,果斷 15.0。。。

合併就是為了壟斷,再無硝煙四起的價格戰,整合成統一價格模式,使用者也就沒有了自由選擇權。

DiDi

在這裡,滴滴相當於主題,司機相當於觀察者。

程式碼實現

建立抽象主題

提供關於註冊、登出、通知觀察者的介面:

// subject.h
#ifndef SUBJECT_H
#define SUBJECT_H

class IObserver;

// 抽象主題
class ISubject
{
public:
    virtual void Attach(IObserver *) = 0;  // 註冊觀察者
    virtual void Detach(IObserver *) = 0;  // 登出觀察者
    virtual void Notify() = 0;  // 通知觀察者
};

#endif // SUBJECT_H

建立具體主題

抽象主題的具體實現,用於管理所有的觀察者:

// concrete_subject.h
#ifndef CONCRETE_SUBJECT_H
#define CONCRETE_SUBJECT_H

#include "subject.h"
#include "observer.h"
#include <iostream>
#include <list>

using namespace std;

// 具體主題
class ConcreteSubject : public ISubject
{
public:
    ConcreteSubject() { m_fPrice = 10.0; }

    void SetPrice(float price) {
        m_fPrice = price;
    }

    void Attach(IObserver *observer) {
        m_observers.push_back(observer);
    }

    void Detach(IObserver *observer) {
        m_observers.remove(observer);
    }

    void Notify() {
        list<IObserver *>::iterator it = m_observers.begin();
        while (it != m_observers.end()) {
            (*it)->Update(m_fPrice);
            ++it;
        }
    }

private:
    list<IObserver *> m_observers;  // 觀察者列表
    float m_fPrice;  // 價格
};

#endif // CONCRETE_SUBJECT_H

建立抽象觀察者

提供一個 Update() 介面,用於更新價格:

// observer.h
#ifndef OBSERVER_H
#define OBSERVER_H

// 抽象觀察者
class IObserver
{
public:
    virtual void Update(float price) = 0;  // 更新價格
};

#endif // OBSERVER_H

建立具體觀察者

抽象觀察者的具體實現,當接收到通知後,調整對應的價格:

// concrete_observer.h
#ifndef CONCRETE_OBSERVER_H
#define CONCRETE_OBSERVER_H

#include "observer.h"
#include <iostream>
#include <string>

using namespace std;

// 具體觀察者
class ConcreteObserver : public IObserver
{
public:
    ConcreteObserver(string name) { m_strName = name; }

    void Update(float price) {
        cout << m_strName << " - price: " << price << "\n";
    }

private:
     string m_strName;  // 名字
};

#endif // CONCRETE_OBSERVER_H

建立客戶端

建立主題以及對應的觀察者,並新增觀察者並更新價格:

// main.cpp
#include "concrete_subject.h"
#include "concrete_observer.h"

#ifndef SAFE_DELETE
#define SAFE_DELETE(p) { if(p){delete(p); (p)=NULL;} }
#endif

int main()
{
    // 建立主題、觀察者
    ConcreteSubject *pSubject = new ConcreteSubject();
    IObserver *pObserver1 = new ConcreteObserver("Jack Ma");
    IObserver *pObserver2 = new ConcreteObserver("Pony");

    // 註冊觀察者
    pSubject->Attach(pObserver1);
    pSubject->Attach(pObserver2);

    // 更改價格,並通知觀察者
    pSubject->SetPrice(12.5);
    pSubject->Notify();

    // 登出觀察者
    pSubject->Detach(pObserver2);
    // 再次更改狀態,並通知觀察者
    pSubject->SetPrice(15.0);
    pSubject->Notify();

    SAFE_DELETE(pObserver1);
    SAFE_DELETE(pObserver2);
    SAFE_DELETE(pSubject);

    getchar();

    return 0;
}

輸出如下:

Jack Ma - price: 12.5
Pony - price: 12.5
Jack Ma - price: 15

開始,我們建立了一個主題(滴滴)以及兩個觀察者(Jack Ma & Pony),通過 attach() 將他們加入至司機行列。呼叫 setPrice(12.5),通知他們起步價為 12.5 元。後來呢,司機 Pony 由於種種原因(~O(∩_∩)O~大家都懂得)離職了 - detach() 登出。。。價格再次上調,漲、漲、漲 setPrice(15.0)。。。