1. 程式人生 > >【Python設計模式】06 觀察者模式-瞭解物件的情況

【Python設計模式】06 觀察者模式-瞭解物件的情況

六、觀察者模式-瞭解物件的情況

這章可以討論行為型設計模式:觀察者設計模式

本章主題

  • 行為型設計模式簡介
  • 觀察者設計模式及其 UML圖
  • 利用 Python3.x程式碼實現一個真實用例
  • 鬆耦合的強大威力
  • 常見問答

1. 行為型模式簡介

  • 建立型模式的工作原理是基於物件的建立機制的。由於這些模式隔離了物件的建立細節,所以使得程式碼能夠與要建立的物件的型別相互獨立。結構型模式用於設計物件和類的結構,從而使它可以相互協作以獲得更大的結構。它們重點關注的是簡化結構以及識別類和物件之間的關係

  • 結構型模式用於設計物件和類的結構,從而使它們可以相互協作以獲得更大的結構。重點在於簡化結構以及識別類和物件之間的關係

  • 行為型模式重點關注的是物件的責任。它們用來處理物件之間的互動,以實現更大的功能。行為型模式:物件之間應該能夠彼此互動,同時還應該是鬆散耦合的

2. 理解觀察者設計模式

在觀察者設計模式中,物件(主題)維護了一個依賴(觀察者)列表,以便主題可以使用觀察者定義的任何方法通知所有觀察者它所發生的變化

觀察者模式的主要目標:

  • 它定義了物件之間的一對多的依賴關係,從而使得一個物件中的任何更改都將自動通知給其他依賴物件
  • 它封裝了主題的核心元件

觀察者模式的應用場景:

  • 在分散式系統中實現事物服務
  • 用作新聞機構的架構
  • 股票市場也是觀察者模式的一個大型場景

Python3.x實現簡單的觀察者模式

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() observer1 = Observer1(subject) observer2 = Observer2(subject) subject.notifyAll("notification")

執行結果:
Observer1 :: Got (‘notification’,) From <main.Subject object at 0x7f9f99fc6518>
Observer2 :: Got (‘notification’,) From <main.Subject object at 0x7f9f99fc6518>


3. 觀察者模式的 UML類圖

觀察者模式UML類圖
觀察者有三個主要角色:主題,觀察者和具體觀察者

  • 主題:類Subject 需要了解Observer。Subject類具有許多方法,諸如 register()和 deregister()等,Observer可以通過這些方法註冊到 Subject類中。因此,一個 Subject可以處理多個 Observer

  • 觀察者:它為關注主題的物件定義了一個介面。它定義了 Observer需要實現的各個方法,以便在主題發生變化時能夠獲得相應的通知

  • 具體觀察者(ConcreteObserver):它用來儲存應該與 Subject的狀態保持一致的狀態。它實現了 Observer介面以保持其狀態與主題中的變化相一致

4. 現實世界中的觀察者模式

我們以新聞機構為例展示觀察者模式的現實世界場景

# coding:utf-8
from abc import abstractmethod, ABCMeta

# 主題:
class NewPublisher:
    def __init__(self):
        self.__subscribers = []
        self.__latestNews = None

    # 提供註冊
    def attch(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.__latestNews = news

    # 返回最新訊息
    def getNews(self):
        return "Got News:", self.__latestNews


# 觀察者:使用者
class Subscriber(metaclass=ABCMeta):
    @abstractmethod
    def update(self):
        pass


# 具體觀察者:SMS使用者
class SMSSubscriber:
    def __init__(self, publisher):
        self.publisher = publisher
        self.publisher.attch(self)

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


# 具體觀察者:郵件使用者
class EmailSubscriber:
    def __init__(self, publisher):
        self.publisher = publisher
        self.publisher.attch(self)

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


# 具體觀察者:其他使用者
class AnyOtherSubscriber:
    def __init__(self, publisher):
        self.publisher = publisher
        self.publisher.attch(self)

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


if __name__ == "__main__":
    news_publisher = NewPublisher()
    for Subscribers in [SMSSubscriber, EmailSubscriber, AnyOtherSubscriber]:
        Subscribers(news_publisher)   # 觀察者,向主題訂閱
    print("\nSubscriber: ", 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()       # 釋出訊息


執行結果:
Subscriber: [‘SMSSubscriber’, ‘EmailSubscriber’, ‘AnyOtherSubscriber’]
SMSSubscriber (‘Got News:’, ‘Hello World!’)
EmailSubscriber (‘Got News:’, ‘Hello World!’)
AnyOtherSubscriber (‘Got News:’, ‘Hello World!’)

Detached: AnyOtherSubscriber

Subscribers: [‘SMSSubscriber’, ‘EmailSubscriber’]
SMSSubscriber (‘Got News:’, ‘My second news’)
EmailSubscriber (‘Got News:’, ‘My second news’)


5. 觀察者通知方式

  1. 拉模型
  • 每當發生變化時,主題都會向所有已註冊的觀察者進行廣播
  • 出現變化時,觀察者負責獲取相應變化情況,或者從訂戶哪裡拉取資料
  • 拉模型效率較低,因為它涉及兩個步驟。第一步,主題通知觀察者;第二步,觀察者從主題那裡提取所需的資料
  1. 推模型
  • 與拉模型不同,變化由主題推送到觀察者
  • 在拉模型中,主題可以向觀察者傳送詳細的資訊。當主題傳送大量觀察者用不到的資料時,會使響應時間過長
  • 由於只從主題傳送所需的資料,所以能夠提高效能

6. 鬆耦合與觀察者

鬆耦合是軟體開發應該採用的重要設計原理之一

  1. 鬆耦合架構特性:
  • 降低一個元素內發生的更改可能對其他元素產生意外影響的風險
  • 它使得測試、維護和故障排除工作更加簡單
  • 系統可以輕鬆地分解為可定義的元素
  1. 觀察者模式:
  • 主題對觀察者唯一瞭解的就是實現一個特定的介面。同時不需要了解具體的觀察者類
  • 可以隨時新增任意的新觀察者
  • 新增新觀察者,根本不需要修改主題
  • 觀察者和主題沒有繫結在一起,可以彼此獨立使用。如果有需要,觀察者可以在任何地方重用
  • 觀察者中的變化不會相互影響

7. 觀察者模式的優點和缺點

  1. 觀察者模式優點:
  • ta使得彼此互動的物件之間保持鬆耦合
  • ta使得我們可以在無需對主題或觀察者進行任何修改的情況下,高效地傳送資料到其他物件
  • 可以隨時新增、刪除觀察者
  1. 觀察者模式缺點:
  • 觀察者介面必須有具體觀察者實現,而這涉及繼承。無法進行組合,因為觀察者介面可以例項化
  • 如果實現不當的話,觀察者可能會增加複雜性,並導致效能降低
  • 在軟體應用程式中,通知有時可能是不可靠的,並導致競爭條件或不一致

8. 常見問題

  1. 可能存在多個主題和觀察者嗎?
    當一個軟體應用程式建立多個主題和觀察者時,是可能的。但是想要正常工作的話,需要通知觀察者哪些主題發生了變化以及各個主題中發生了哪些變化

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

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

9. 小結

  1. 簡要介紹行為型模式
  2. 觀察者模式知識,以及如何將其高效地應用於軟體架構中
  3. 考察了觀察者模式如何用來通知觀察者在主題中發生的變化。它們不僅能夠管理物件之間的互動,同時還能管理物件的一對多的依賴關係
  4. 學習了觀察者設計模式的 UML類圖和基於 Python3.x的例項實現程式碼
  5. 觀察者模式的兩種不同實現方式:推模型和拉模型
  6. 軟體設計的鬆耦合原理
  7. 常見問題的解答,瞭解觀察者模式的思想以及優缺點