細說Python之觀察者模式(行爲型模式)

行爲型模式介紹

  • 前面講了創建型模式(單例模式)、結構型模式(門面模式),現在講講行爲型模式(觀察者模式)。
  • 創建型模式的工作原理是基於對象的創建機制的。由於這些模式隔離了對象的創建細節,所以使得代碼能夠與要創建的對象的類型相互獨立。結構型模式用於設計對象和類的結構,從而使它們可以相互協作以獲得更大的結構。它們重點關注的是簡化結構以及識別類和對象之間的關係。
  • 行爲型模式,它主要關注的是對象的責任。它們用來處理對象之間的交互,以實現更大的功能。行爲型模式建議:對象之間應該能夠彼此交互,同時還應該是鬆散耦合的。

理解觀察者設計模式

在觀察者設計模式中,對象(主題)維護了一個依賴(觀察者)列表,以便主題可以使用觀察者定義的任何方法通知所有觀察者它所發生的變化。
在分佈式應用的世界中,多個服務通常是通過彼此交互來實現用戶想要實現的更大型的操作的。服務可以執行多種操作,但是它們執行的操作會直接或很大程度上取決於與其交互的服務對象的狀態。
關於用戶註冊的示例,其中用戶服務負責用戶在網站上的各種操作。假設我們有另一個稱爲電子郵件服務的服務,它的作用是監視用戶的狀態並向用戶發送電子郵件。例如,如果用戶剛剛註冊,則用戶服務將調用電子郵件服務的方法,該方法將向用戶發送電子郵件以進行賬戶驗證。如果賬戶經過了驗證,但信用度較低,則電子郵件服務將監視用戶服務並向用戶發送信用度過低的電子郵件警報。
因此,如果在應用中存在一個許多其他服務所依賴的核心服務,那麼該核心服務就會成爲觀察者必須觀察/監視其變化的主題。當主題發生變化時,觀察者應該改變自己的對象的狀態,或者採取某些動作。這種情況(其中從屬服務監視核心服務的狀態變化)描述了觀察者設計模式的經典情景。
在廣播或發佈/訂閱系統的情形中,你會看到觀察者設計模式的用法。我們不妨考慮博客的例子,假設你是一個技術愛好者,喜歡閱讀這個博客中Python方面的最新文章。這時你會怎麼做?當然是訂閱該博客。跟你一樣,許多訂閱者也會在這個博客中註冊。所以,每當發佈新博客時,你就會收到通知,或者如果原來的博客發生了變化,你也會收到通知。當然,通知你發生改變的方式可以是電子郵件。現在,如果將此場景應用於觀察者模式,那麼這裏的博客就是維護訂閱者或觀察者列表的主題。因此,當有新的文章添加到博客中時,所有觀察者就會通過電子郵件或由觀察者定義任何其他通知機制收到相應的通知。
觀察者模式的主要目標如下:

  • 它定義了對象之間的一對多的依賴關係,從而使得一個對象中的任何更改都將自動通知給其他依賴對象;
  • 它封裝了主題的核心組件。
    觀察者適用的場景:
  • 在分佈式系統中實現事件服務;
  • 用作新聞機構的框架;
  • 股票市場也是觀察者模式的一個大型場景。

觀察者模式的簡單實現:

# -*- coding: utf-8 -*-
# @Time    : 2020/6/24 上午 09:28
# @Author  : lh
# @Email   : [email protected]
# @File    : test.py
# @Software: PyCharm

class Subject:
    def __init__(self):
        self.__observers = []

    def register(self, observer):
        self.__observers.append(observer)

    def notifyAll(self, *args, **kwargs):
        for observer in self.__observers:
            observer.notify(self, *args, **kwargs)


class Observer1:
    def __init__(self, subject):
        subject.register(self)

    def notify(self, subject, *args):
        print(type(self).__name__, "::Got", args, "From", subject)


class Observer2:
    def __init__(self, subject):
        subject.register(self)

    def notify(self, subject, *args):
        print(type(self).__name__, "::Got", args, "From", subject)


subject = Subject()
objserver1 = Observer1(subject)
objserver2 = Observer2(subject)
subject.notifyAll("notification")

在這裏插入圖片描述

觀察者模式的UML類圖:
在這裏插入圖片描述
這個模式有3個主要角色:

  • 主題(Subject):類Subject需要了解Observer。Subject類具有許多方法,諸如register( )和deregister( )等,Observer可以通過這些方法註冊到Subject類中。因此,一個Subject可以處理多個Observer。
  • 觀察者(Observer):它爲關注主題的對象定義了一個接口。它定義了Observer需要實現的各個方法,以便在主題發生變化時能夠獲得相應的通知。
  • 具體觀察者(ConcreteObserver):它用來保存應該與Subject的狀態保持一致的狀態。它實現了Observer接口以保持其狀態與主題中的變化相一致。
  • 這個流程非常簡單。具體觀察者通過實現觀察者提供的接口向主題註冊自己。每當狀態發生變化時,該主題都會使用觀察者提供的通知方法來通告所有具體觀察者。

現實世界中的觀察者模式

以新聞機構爲例來展示觀察者模式的現實世界場景。新聞機構通常從不同地點收集新聞,並將其發佈給訂閱者。

由於信息是實時發送或接收的,所以新聞機構應該儘快向其訂戶公佈該消息。此外,隨着技術的進步,訂戶不僅可以訂閱報紙,而且可以通過其他的形式進行訂閱,例如電子郵件、移動設備、短信或語音呼叫。所以,我們還應該具備在將來添加任意其他訂閱形式的能力,以便爲未來的新技術做好準備。

實例:test.py:我把他們分段了,其實都是在同一個文件下面的。

"""
主題的行爲由NewsPublisher類表示;
NewsPublisher提供了一個供訂戶使用的接口;
attach( )方法供觀察者(Observer) 來註冊NewsPublisherObserver,detach()方法用於註銷;
subscriber( )方法返回已經使用Subject註冊的所有訂戶的列表;
notifySubscriber( )方法可以用來遍歷已向NewsPublisher註冊的所有訂戶;
發佈者可以使用addNews( )方法創建新消息,getNews( )用於返回最新消息,並通知觀察者。
"""


class NewsPublisher:
    def __init__(self):
        self.__subscribers = []
        self.__latesNews = None

    def attach(self, subscriber):
        self.__subscribers.append(subscriber)

    def detach(self):
        return self.__subscribers.pop()

    def subscribers(self):
        return [type(x).__name__ for x in self.__subscribers]

    def notifySubscribers(self):
        for sub in self.__subscribers:
            sub.update()

    def addNews(self, news):
        self.__latesNews = news

    def getNews(self):
        return "Got News:", self.__latesNews
from abc import ABCMeta, abstractmethod

"""
Subscriber表示Observer,它是一個抽象的基類,代表其他ConcreteObserver;
Subscriber有一個update( )方法,但是它需要由ConcreteObservers實現;
Subscriber有一個update( )方法,但是它需要由ConcreteObservers實現;update( )方法是由ConcreteObserver實現的,這樣只要有新聞發佈的時候,它們都能得到Subject``(NewsPublishers)的相應通知。
"""


class Subscriber(metaclass=ABCMeta):
    @abstractmethod
    def update(self):
        pass

"""
有兩個主要觀察者,分別是實現訂戶接口的EmailSubscriber和SMSSubscriber;
除了這兩個之外,我們建立了另一個觀察者AnyOtherObserver,它是用來演示Observers與Subject的鬆散耦合關係的;
每個具體觀察者的__init __( )方法都是使用attach()方法向NewsPublisher進行註冊的;
具體觀察者的update( )方法由NewsPublisher在內部用來通知添加了新的新聞。
"""


class SMSSubscriber(Subscriber):
    def __init__(self, publilsher):
        self.publisher = publilsher
        self.publisher.attach(self)

    def update(self):
        print(type(self).__name__, self.publisher.getNews())


class EmailSubscriber(Subscriber):
    def __init__(self, publisher):
        self.publisher = publisher
        self.publisher.attach(self)

    def update(self):
        print(type(self).__name__, self.publisher.getNews())


class AnyOtherSubscriber(Subscriber):
    def __init__(self, publisher):
        self.publisher = publisher
        self.publisher.attach(self)

    def update(self):
        print(type(self).__name__, self.publisher.getNews())

"""
所需的訂戶都已經實現好了,下面讓我們來考察NewsPublisher和SMSSubscribers類。
客戶端爲NewsPublisher創建一個對象,以供具體觀察者用於各種操作。
使用發佈者的對象初始化SMSSubscriber、EmailSubscriber和AnyOther Subscriber類。
在Python中,當我們創建對象時,__init __( )方法就會被調用。在ConcreteObserver類中,__init __( )方法在內部使用NewsPublisher的attach( )方法進行註冊以獲取新聞更新。
然後,我們打印出已經通過主題註冊的所有訂戶(具體觀察者)的列表。
接着,使用newsPublisher(news_publisher)的對象通過addNews( )方法創建新消息。NewsPublisher的notifySubscribers( )方法用於通知所有訂戶出現了新消息。notifySubscribers( )方法在內部調用由具體觀察者實現的update()方法,以便它們可以獲得最新的消息。NewsPublisher還提供了detach( )方法,可從註冊訂戶列表中刪除訂戶。
"""
if __name__ == "__main__":
    news_publisher = NewsPublisher()
    for Subscribers in [SMSSubscriber, EmailSubscriber, AnyOtherSubscriber]:
        Subscribers(news_publisher)
    print("\nSubscribers:", news_publisher.subscribers())
    news_publisher.addNews("Hello World!")
    news_publisher.notifySubscribers()
    print("\nDetached:", type(news_publisher.detach()).__name__)
    print("\nSubscribers:", news_publisher.subscribers())
    news_publisher.addNews("My second news!")
    news_publisher.notifySubscribers()

上面代碼的輸出結果:
在這裏插入圖片描述

觀察者模式的通知模式

有兩種不同的方式可以通知觀察者在主題中發送的變化。他們可以被分爲推模型或拉模型。

拉模型

在拉模型中,觀察者扮演積極的角色。

  • 每當發送變化時,主題都會向所有已註冊的觀察者進行廣播。
  • 出現變化時,觀察者負責獲取相應的變化情況,或者從訂戶那裏拉取數據。
  • 拉模型的效率較低,因爲它涉及兩個步驟,第一步,主題通知觀察者,第二步,觀察者從主題那裏提取所需的數據。
推模型

在推模型中,主題是起主導作用的一方。

  • 與拉模型不同,變化是由主題推送到觀察者的。
  • 在拉模型中,主題可以向觀察者發送詳細的信息(即使可能不需要)。當主題發送大量觀察者用不到的數據時,會使響應時間過長。
  • 由於只從主題發送所需的數據,所以能夠提高性能。

松耦合與觀察者模式

松耦合是軟件開發應該採用的重要設計原理之一。松耦合的主要目的是爭取在彼此交互的對象之間實現鬆散耦合設計。耦合是指一個對象對於其交互的其他對象的瞭解程度。

松耦合設計允許我們構建靈活的面向對象的系統,有效應對各種變化,因爲它們降低了多個對象之間的依賴性。

松耦合架構特性:

  • 它降低了在一個元素內發生的更改可能對其他元素產生意外影響的風險。
  • 它使得測試、維護和故障排除工作更加簡單。
  • 系統可以輕鬆地分解爲可定義的元素。

觀察者模式提供了一種實現主題和觀察者松耦合的對象設計模式。:

  • 主題對觀察者唯一的瞭解就是它實現一個特定的接口。同時,它也不需要了解具體觀察者類。
  • 可以隨時添加任意的新的觀察者。
  • 添加的新的觀察時,根本不需要修改主題。實例中,可以看到其它觀察者可以任意添加/刪除,而無需在主題中進行任何的更改。
  • 觀察者或主題沒有綁定在一起,所以可以彼此獨立使用。如果需要的話,觀察者可以在任何地方重複使用。
  • 主題或觀察者中的變化不會互相影響。由於兩者都是獨立的或鬆散耦合的,所以它們可以自由地做出自己的改變。
觀察者模式的優缺點

優點:

  • 它使得彼此交互的對象之間保持松耦合。
  • 它使得我們可以在無需對主題或觀察者進行任何修改的情況下高效地發送數據到其他對象。
  • 可以隨時添加/刪除觀察者。

缺點:

  • 觀察者接口必須有具體觀察者實現,而這涉及繼承。無法進行組合,因爲觀察者接口可以實例化。
  • 如果實現不當的話,觀察者可能會增加複雜性,並導致性能降低。
  • 在軟件應用程序中,通知有時可能是不可靠的,並導致競爭條件或不一致性。

問答環節:

  1. 可能存在多個主題和觀察者嗎?
    答:當一個軟件應用程序建立了多個主題和觀察者的時候,是可能的。在這種情況下,要想正常工作,需要通知觀察者哪

  2. 誰負責觸發更新?
    答:觀察者模式可以在推模型和拉模型中工作。通常情況下,當發生更新時,主題會觸發更新方法,但有時可以根據應用程序的需要,觀察者也是可以觸發通知的。然而,需要注意的是頻率不應該太高,否則可能導致性能下降,特別是當主題的更新不太頻繁時。

  3. 主題或觀察者可以在任何其他用例中訪問嗎?
    答:是的,這就是鬆散耦合的力量在觀察者模式中的強大體現。主題和觀察者是可以獨立使用的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章