1. 程式人生 > 實用技巧 >【設計模式】觀察者模式

【設計模式】觀察者模式

觀察者模式

簡介

軟體系統中的物件並不是孤立存在的,一個物件行為的改變可能會引起其他關聯的物件的狀態或行為也發生改變。觀察者模式建立了一種一對多的聯動,一個物件改變時將自動通知其他物件,其他物件將作出反映。

觀察者模式中,發生改變的物件稱為"觀察目標",被通知的物件稱為"觀察者"。一個觀察目標可可以有多個觀察者

觀察者模式又稱為釋出-訂閱模式(Publish-Subscibe)、模型-檢視模式(Model-View)、源-監聽器模式(Source-Listener)、從屬者模式(Dependents)。

觀察者模式:定義物件之間的一種一對多的依賴關係,使得每當一個物件狀態發生改變時,其相關依賴物件都得到通知並被自動更新。

結構

實現

實現方式:

  • 仔細檢查業務邏輯,試著將其拆分為兩個部分:獨立於其他程式碼的核心功能將作為釋出者;其他程式碼則將轉化為一組訂閱類。
  • 宣告訂閱者介面。該介面至少應宣告一個update方法。
  • 聲明發布者介面並定義一些介面來在列表中新增和刪除訂閱物件。記住釋出者必須僅通過訂閱者與它們進行互動。
  • 確定存放實際訂閱列表的位置並實現訂閱方法。通常所有型別的釋出者程式碼看上去都一樣,因此將列表放置在直接拓展自發布者介面的抽象類是顯而易見的。具體釋出者會擴充套件該類從而繼承所有的訂閱行為。
  • 建立具體釋出者類。每次釋出者發生了重要事件時都必須通知所有的訂閱者。
  • 在具體訂閱者類中實現通知更新的方法。絕大部分訂閱者需要一些與事件相關的上下文資料,這些資料可作為通知方法的引數來傳遞。
  • 客戶端必須生成所需的全部訂閱者,並在相應的釋出者處完成註冊工作。
#include <iostream>
#include <string>
#include <list>


// 抽象訂閱者
class BaseSubscriber {
public:
    virtual ~BaseSubscriber() {}
    virtual void update(const std::string &msgFromPublisher) = 0;
};

// 抽象釋出者
class BasePublisher {
public:
    virtual ~BasePublisher() {}
    virtual void attach(BaseSubscriber *subscriber) = 0;
    virtual void detach(BaseSubscriber *subscriber) = 0;
    virtual void notify() = 0;
};

// 具體釋出者
class Publisher : public BasePublisher {
private:
    std::list<BaseSubscriber*> m_subscribers;
    std::string m_msg;

public:
    virtual ~Publisher() {
        std::cout << "釋出者離開\n";
    }
    void attach(BaseSubscriber *subscriber) override {
        m_subscribers.push_back(subscriber);
    }
    void detach(BaseSubscriber *subscriber) override {
        m_subscribers.remove(subscriber);
    }
    void notify() override {
        std::list<BaseSubscriber*>::iterator it = m_subscribers.begin();
        getSubscriberCnt();
        while (it != m_subscribers.end())
        {
            (*it)->update(m_msg);
            ++it;
        }
    }
    void createMsg(std::string msg = "NULL") {
        std::cout << "釋出者釋出訊息 ---> " << msg << "\n";
        this->m_msg = msg;
        notify();
    }
    void getSubscriberCnt() {
        std::cout << "當前訂閱者個數為:" << m_subscribers.size() << "\n";
    }
    void doSomeBusinessLogic() {
        m_msg = "change msg";
        notify();
        std::cout << "需要做一些重要的事" <<"\n";
    }
};

// 具體訂閱者
class Subscriber: public BaseSubscriber {
private:
    std::string m_msgFromPublisher;
    Publisher &m_publisher;
    static int m_staticNumber;
    int m_number;

public:
    Subscriber(Publisher &publisher) : m_publisher(publisher) {
        m_publisher.attach(this);
        std::cout << "我是訂閱者\"" << ++Subscriber::m_staticNumber << "\"\n";
        m_number = Subscriber::m_staticNumber;
    }
    virtual ~Subscriber() {
        std:: cout << "訂閱者\"" << m_number <<"\"離開\n";
    }
    void update(const std::string &msgFromPublisher) override {
        m_msgFromPublisher = msgFromPublisher;
        printInfo();
    }
    void printInfo() {
        std::cout << "訂閱者\"" << m_number << "\":  接收到新訊息 ---> " << m_msgFromPublisher << "\n";
    }
    void removeMeFromList() {
        m_publisher.detach(this);
        std::cout << "訂閱者\"" << m_number <<"\"從列表中移除\n";
    }
};

int Subscriber::m_staticNumber = 0;

void ClientCode() {
    Publisher *publisher = new Publisher;
    Subscriber *subscribe1 = new Subscriber(*publisher);
    Subscriber *subscribe2 = new Subscriber(*publisher);
    Subscriber *subscribe3 = new Subscriber(*publisher);
    Subscriber *subscribe4;
    Subscriber *subscribe5;
    std::cout << "\n";

    publisher->createMsg("Hello World!");
    std::cout << "\n";

    subscribe3->removeMeFromList();
    std::cout << "\n";

    publisher->createMsg("今天天氣怎麼樣?");
    std::cout << "\n";

    subscribe4 = new Subscriber(*publisher);
    std::cout << "\n";

    subscribe2->removeMeFromList();
    std::cout << "\n";

    subscribe5 = new Subscriber(*publisher);
    std::cout << "\n";

    publisher->createMsg("通知:下午放假!");
    std::cout << "\n";

    subscribe5->removeMeFromList();
    std::cout << "\n";

    subscribe4->removeMeFromList();
    subscribe1->removeMeFromList();
    std::cout << "\n";

    delete subscribe5;
    delete subscribe4;
    delete subscribe3;
    delete subscribe2;
    delete subscribe1;
    delete publisher;
    std::cout << "\n";
}

int main(int argc, char *argv[]) {
    ClientCode();
    return 0;
}
# -*- coding: utf-8 -*-

from __future__ import annotations
from abc import ABC, abstractmethod
from random import randrange
from typing import List


class Publisher(ABC):
    """
    抽象釋出者
    """
    @abstractmethod
    def attach(self, subscriber: Subscriber) -> None:
        pass

    @abstractmethod
    def detach(self, subscriber: Subscriber) -> None:
        pass

    @abstractmethod
    def notify(self) -> None:
        pass


class ConcretePublisher(Publisher):
    """
    具體釋出者
    """
    _state: int = None
    _subscribers: List[Subscriber] = []

    def attach(self, subscriber: Subscriber) -> None:
        print(f"釋出者:添加了訂閱者{subscriber._name}")
        self._subscribers.append(subscriber)

    def detach(self, subscriber: Subscriber) -> None:
        print(f"釋出者:移除了訂閱者{subscriber._name}")
        self._subscribers.remove(subscriber)

    def notify(self) -> None:
        print("釋出者:釋出了訊息")
        for subscriber in self._subscribers:
            subscriber.update(self)

    def do_some_business_logic(self) -> None:
        print("釋出者:執行一些事情")
        self._state = randrange(0, 10)
        print(f"釋出者:目前我的狀態為{self._state}")
        self.notify()


class Subscriber(ABC):
    """
    """
    @abstractmethod
    def update(self, publisher: Publisher) -> None:
        pass


class ConcreteSubscriberA(Subscriber):
    """
    """
    _name = "A"

    def update(self, publisher: Publisher) -> None:
        if publisher._state < 3:
            print("訂閱者A:通過過濾規則後,接收到訊息")


class ConcreteSubscriberB(Subscriber):
    """
    """
    _name = "B"

    def update(self, publisher: Publisher) -> None:
        if publisher._state >= 2 or publisher._state == 0:
            print("訂閱者B:通過過濾規則後,接收到訊息")


if __name__ == "__main__":

    publisher = ConcretePublisher()

    subscriberA = ConcreteSubscriberA()
    publisher.attach(subscriberA)

    subscriberB = ConcreteSubscriberB()
    publisher.attach(subscriberB)

    publisher.do_some_business_logic()
    publisher.do_some_business_logic()
    publisher.do_some_business_logic()

    publisher.detach(subscriberA)

    publisher.do_some_business_logic()
    publisher.do_some_business_logic()

例項

問題描述

模擬手遊和平精英的"我這裡有物資"和"救救我"等訊息通知。

問題解答

// Example.cpp

#include <iostream>
#include <string>
#include <list>
#include <algorithm>


enum INFO_TYPE {
    NONE,
    RESOURCE,
    HELP
};

class BasePublisher;

// 抽象訂閱者
class BaseSubscriber {
private:
    std::string m_name;

public:
    virtual ~BaseSubscriber() {}
    virtual void call(INFO_TYPE type, BasePublisher *publisher) = 0;
    void setName(const std::string &name) {
        m_name = name;
    }
    std::string getName(){ 
        return m_name;
    }
    void come() {
        std::cout <<"玩家 " << m_name << ": 收到,我來取物資!\n";
    }
    void help() {
        std::cout <<"玩家 " << m_name << ": 堅持住,我來救你!\n";
    }
};

// 抽象釋出者
class BasePublisher {
public:
    virtual ~BasePublisher() {}
    virtual void join(BaseSubscriber *subscriber) = 0;
    virtual void leave(BaseSubscriber *subscriber) = 0;
    virtual void notify(INFO_TYPE type, const std::string name) = 0;
};

// 具體釋出者:戰隊中心
class PlayerCenter : public BasePublisher {
private:
    std::list<BaseSubscriber*> m_subscribers;

public:
    void join(BaseSubscriber *subscriber) override {
        if (m_subscribers.size() < 4) {
            std::cout << "玩家 " << subscriber->getName() << ": 加入戰隊!\n";
            m_subscribers.push_back(subscriber);
        } else {
            std::cout << "戰隊玩家已滿,無法加入!\n";
            return;
        }
        if(m_subscribers.size() == 4) {
            std::cout << "組隊成功,不要慫,一起上!\n";
        }
    }
    void leave(BaseSubscriber *subscriber) override {
        if (std::find(m_subscribers.begin(), m_subscribers.end(), subscriber) != m_subscribers.end()) {
            std::cout << "玩家 " << subscriber->getName() << ": 離開戰隊!\n";
            m_subscribers.remove(subscriber);
        } else {
            std::cout << "玩家 " << subscriber->getName() << ": 不在戰隊!\n";
        }
    }
    void notify(INFO_TYPE type, const std::string name) override {
        if(type == RESOURCE) {
            for(const auto &subscriber : m_subscribers) {
                if (subscriber->getName() != name) {
                    subscriber->come();
                }
            }

        } else if(type == HELP) {
            for(const auto &subscriber : m_subscribers) {
                if (subscriber->getName() != name) {
                    subscriber->help();
                }
            }
        } else {
            ;
        }
    }
};

class Player: public BaseSubscriber {
public:
    Player(std::string name) {
        setName(name);
    }
    void call(INFO_TYPE type, BasePublisher *publisher) override {
        if(type == RESOURCE) {
           std::cout <<"玩家 " << getName() << ": 我這裡有物資!\n";
        } else if (type == HELP) {
            std::cout <<"玩家 " << getName() << ": 救救我!\n";
        } else {
            ;
        }
        publisher->notify(type, getName());
    }
};


int main(int argc, char * argv[]) {
    PlayerCenter *playerCenter = new PlayerCenter();

    Player *A = new Player("A");
    Player *B = new Player("B");
    Player *C = new Player("C");
    Player *D = new Player("D");
    playerCenter->join(A);
    playerCenter->join(B);
    playerCenter->join(C);
    playerCenter->join(D);
    // playerCenter->join(D);
    std::cout << "\n";

    B->call(RESOURCE, playerCenter);
    std::cout << "\n";

    A->call(HELP, playerCenter);
    std::cout << "\n";

    playerCenter->leave(A);
    playerCenter->leave(B);
    playerCenter->leave(C);
    playerCenter->leave(D);
    std::cout << "\n";

    delete playerCenter;
    delete A;
    delete B;
    delete C;
    delete D;

    return 0;
}

總結

觀察者模式是一種使用頻率非常高的設計模式,幾乎無處不在。凡是涉及一對一、一對多的物件互動場景,都可以使用觀察者會模式。比如購物車,瀏覽商品時,往購物車裡新增一件商品,會引起UI多方面的變化(購物車裡商品數量、對應商鋪的顯示、價格的顯示等);各種程式語言的GUI事件處理的實現;所有的瀏覽器事件(mouseover,keypress等)都是使用觀察者模式的例子。

優點

  • 觀察者模式實現了穩定的訊息更新和傳遞的機制,通過引入抽象層可以擴充套件不同的具體觀察者角色。
  • 支援廣播通訊。所有已註冊的觀察者(新增到目標列表中的物件)都會得到訊息更新的通知,簡化了一對多設計的難度。
  • 符合開閉原則。增加新的觀察者無需修改已有程式碼,在具體觀察者與觀察目標之間不存在關聯關係的情況下增加新的觀察目標也很方便。

缺點

  • 程式碼中觀察者和觀察目標相互引用,存在迴圈依賴,觀察目標會觸發二者迴圈呼叫,有引起系統崩潰的風險。
  • 如果一個觀察目標物件有很多直接和間接觀察者,將所有的觀察者都通知到會耗費大量時間。
  • 訂閱者的通知順序是隨機的。除非特別控制。

場景

  • 當一個物件狀態的改變需要改變其他物件,或實際物件是事先未知的或動態變化的時,可使用觀察者模式。
  • 當應用中的一些物件必須觀察其他物件時,可使用觀察者模式。但僅能在有限時間內或特定情況下使用。

與其他模式的關係

  • 責任鏈模式命令模式中介者模式觀察者模式用於處理請求傳送者和接收者之間的不同連線方式:
    • 責任鏈按照順序將請求動態傳遞給一系列的潛在接收者,直至其中一名接收者對請求進行處理。
    • 命令在傳送者和請求者之間建立單向連線。
    • 中介者清除了傳送者和請求者之間的直接連線,強制它們通過一箇中介物件進行間接溝通。
    • 觀察者允許接收者動態地訂閱或取消接收請求。
  • 中介者模式觀察者模式之間往往很難區分。在大部分情況下,可以使用其中一種模式,而有時可以同時使用。
    • 中介者的主要目標是消除一系列系統元件之間的相互依賴。這些元件將依賴於同一個中介者物件。觀察者的目標是在物件之間建立動態的單向連線,使得部分物件可作為其他物件的附屬發揮作用。
    • 有一種流行的中介者模式實現方式依賴於觀察者。中介者物件擔當釋出者的角色,其他元件則作為訂閱者,可以訂閱中介者的事件或取消訂閱。當中介者以這種方式實現時,它可能看上去與觀察者非常相似。
    • 假設有一個程式,其所有的元件都變成了釋出者,它們之間可以相互建立動態連線。這樣程式中就沒有中心化的中介者物件,而只有一些分散式的觀察者。