Python 設計模式:觀察者模式">Python 設計模式:觀察者模式

分類:IT技術 時間:2017-09-26

題目 :現在你有一個數字,默認格式化程序是以十進制格式展示此數值,但需要提供一個功能,這個程序要支持添加/註冊更多的格式化程序(比如:添加一個十六進制格式化程序和一個二進制格式化程序)。每次數值更新時,已註冊的程序就會收到通知,並顯示更新後的值。

我們看下需求:

  1. NumberFormatter 有一個 number 屬性

  2. 當 number 值修改時,相關的格式化方式展示結果要改變

  3. 此系統必須可擴展已適應其他格式化方式的使用。

一個錯誤的實現可能是這樣的:

class NumberFormatter(object):
    def __init__(self, number):
        self.number = number
        
    def show_data(self):
        self.default_formatter()
        self.hex_formatter()
        self.binary_formatter()
        
    def default_formatter(self):
        pass
        
    def hex_formatter(self):
        pass
        
    def binary_formatter(self):
        pass

我們可以這麽使用:

number = NumberFormatter(10)
number.show_data()

但是這樣會有一個問題: 這種針對實現的編程會導致我們在增加或者刪除需要格式化方式時必須修改代碼。 比如我們現在不再需要十六進制數字格式的顯示,就需要把 hex_formatter  相關的代碼刪除或者註釋掉。

要解決這個問題,就可以用到我們這次要介紹的 觀察者模式 了。

什麽是觀察者模式

認識觀察者模式

我們先看看報紙和雜誌的訂閱是怎麽回事:

  1. 報社的業務就是出版報紙

  2. 向某家報社訂閱報紙,只要他們有新報紙,就會給你送來,只要你是他們的訂戶,你就會一直受到新報紙。

  3. 當你不再想看的時候,取消訂閱,他們就不會在送新報紙給你

  4. 只要報社還在運營,就會一直有人向他們訂閱報紙或取消訂閱。

我們用圖表示一下,這裏 出版者  改稱為 主題(Subject)訂閱者 改稱為 觀察者(Observer)

1.  開始的時候,鴨子對象不是觀察者 2.  鴨子對象過來告訴主題,它想當一個觀察者(鴨子其實想說的是:我對你的數據改變感興趣,一有變化請通知我) 3.  鴨子對象已經是觀察者了(鴨子靜候通知,一旦接到通知,就會得到一個整數)。 4.  主題有了新的數據(現在鴨子和其他所有觀察者都會受到通知: 主題已經改變 5.  老鼠對象要求從觀察者中把自己除名(老鼠已經觀察次主題太久,決定不再當觀察者了)。 6.  老鼠離開了(主題知道老鼠的請求後,把它從觀察者中移除了)。 7.  主題有了一個新的整數(除了老鼠之外,每個觀察者都會收到通知,如果老鼠又想當觀察者了,它還可以再回來)

定義觀察者模式

當你試圖勾勒觀察者模式時,可以利用報紙訂閱服務,以及出版這和訂閱者比你這一切。在程序設計中,觀察者模式通常被定義為:

觀察者模式 定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態是,它的所有依賴者都會收到通知並自動更新。

我們和之前的例子做個對比:

主題和觀察者定義了一對多的關系。觀察者依賴於此主題,只要主題狀態一有變化,觀察者就會被通知。根據通知的風格,觀察者可能因此新值而更新。

現在你可能有疑問,這和一對多的關系有何關聯?

利用觀察者模式,主題是具有狀態的對象,並且可以控制這些狀態。也就是說,有 一個 具有狀態的主題。另一方面,觀察者使用這些狀態,雖然這些狀態不屬於他們。有許多觀察者,依賴主題告訴他們狀態何時改變了。這就產生了一個關系: 一個主題對多個觀察者的關系

觀察者和主題之間的依賴關系是如何產生的?

主題是真正擁有數據的人,觀察者是主題的依賴者,在數據變化時更新,這樣比起讓許多對象控制同一份數據來,可以得到更幹凈的 OO 設計。

觀察者模式的應用案例

觀察者模式在實際應用中有許多的案例,比如信息的聚合。無論格式為 RSS、Atom 還是其它,思想多事一樣的:你追隨某個信息源,當它每次更新時,你都會收到關於更新的通知。 事件驅動系統是一個可以使用觀察者模式的例子。在這種系統中,監聽者被用於監聽特定的事件。監聽者的事件被創建出來時就會觸發它們。這個事件可以使鍵入某個特定的鍵、移動鼠標或者其他。事件扮演發布者的角色,監聽者則扮演觀察者的角色。

python 實現

現在,讓我們回到文章開始的那個問題。

這裏我們可以實現一個基類 Publisher,包括添加、刪除及通知觀察者這些公用功能。DefaultFormatter 類繼承自 Publisher,並添加格式化程序特定的功能。

Publisher 的代碼如下:

import itertools

'''
觀察者模式實現
'''

class Publisher:

    def __init__(self):
        self.observers = set()

    def add(self, observer, *observers):
        for observer in itertools.chain((observer, ), observers):
            self.observers.add(observer)
            observer.update(self)
        else:
            print('Failed to add: {}'.format(observer))

    def remove(self, observer):
        try:
            self.observers.discard(observer)
        except ValueError:
            print('Failed to remove: {}'.format(observer))

    def notify(self):
        [observer.update(self) for observer in self.observers]

現在,打算使用觀察者模式的模型或類都應該繼承 Publisher 類。該類用 set 來保存觀察者對象。當用戶向 Publisher 註冊新的觀察者對象時,觀察者的 update() 方法會執行,這使得它能夠用模型當前的狀態初始化自己。模型狀態發生變化時,應該調用繼承而來的 notify() 方法,這樣的話,就會執行每個觀察者對象的 update() 方法,以確保他們都能反映出模型的最新狀態。

add()  方法的寫法值得註意,這裏是為了支持可以接受一個或多個觀察者對象。這裏我們采用了 itertools.chain()   方法,它可以接受任意數量的 iterable ,並返回單個 iterable 。遍歷這個 iterable,也就相當於依次遍歷參數裏的那些 iterable。

接下來是 DefaultFomatter   類。 __init__()   做的第一件事就是調用基類的 __init__()   方法,因為這在 Python 中沒法自動完成。 DefaultFormatter   實例有自己的名字,這樣便於我們跟蹤其狀態。對於 _data   變量,我們使用了名稱改編來聲明不能直接訪問該變量。 DefaultFormatter  _data   變量用作一個整數,默認值為0。

class DefaultFormatter(Publisher):

    def __init__(self, name):
        Publisher.__init__(self)
        self.name = name
        self._data = http://www.tuicool.com/articles/0

    def __str__(self):
        return "{}:'{}' has data = http://www.tuicool.com/articles/{}".format(type(self).__name__, self.name, self._data)

    @property
    def data(self):
        return self._data

    @data.setter
    def data(self, new_value):
        try:
            self._data = int(new_value)
        except ValueError as e:
            print('Error: {}'.format(e))
        else:
            self.notify()
  • __str__()  方法返回關於發布者名稱和   _data   值的信息。type(self).__name 是一種獲取類名的方便技巧,避免硬編碼類名。(不過這會降低代碼的可讀性)

  • data()  方法有兩個,第一個使用了   @property   裝飾器來提供_data 變量的讀訪問方式。這樣,我們就能使用   object.data   來代替 object._data 。第二個 data() 方法使用了 @setter   裝飾器,改裝飾器會在每次使用賦值操作符(=)為 _data   變量賦值時被調用。該方法也會嘗試把新值強制轉換為一個整數,並在轉換失敗時處理異常。

接下來是添加觀察者。 HexFormatter  和   BinaryFormatter   功能基本相似。唯一的不同在於如何格式化從發布者那獲取到的數據值,即十六進制和二進制格式化。

class HexFormatter:

    def notify(self, publisher):
        print("{}: '{}' has now hex data= http://www.tuicool.com/articles/{}".format(type(self).__name__,
                                                     publisher.name, hex(publisher.data)))

class BinaryFormatter:

    def notify(self, publisher):
        print("{}:'{}' has now bin data= http://www.tuicool.com/articles/{}".format(type(self).__name__,
                                                     publisher.name, bin(publisher.data)))

接下來我們添加一下測試數據,運行代碼觀察一下結果:

def main():
    df = DefaultFormatter('test1')
    print(df)

    print()
    hf = HexFormatter()
    df.add(hf)
    df.data = http://www.tuicool.com/articles/3
    print(df)

    print()
    bf = BinaryFormatter()
    df.add(bf)
    df.data = 21
    print(df)

    print()
    df.remove(hf)
    df.data = 40
    print(df)

    print()
    df.remove(hf)
    df.add(bf)

    df.data ='hello'
    print(df)

    print()
    df.data = http://www.tuicool.com/articles/4.2
    print(df)


if __name__ =='__main__':
    main()

完整代碼參考:https://gist.github.com/gusibi/93a000c79f3d943dd58dcd39c4b547f1

運行代碼:

python observer.py    
## output
DefaultFormatter: 'test1' has data = http://www.tuicool.com/articles/0

HexFormatter:'test1' has now hex data= http://www.tuicool.com/articles/0x0
Failed to add: <__main__.HexFormatter object at 0x10277da20>
HexFormatter:'test1' has now hex data= http://www.tuicool.com/articles/0x3
DefaultFormatter:'test1' has data = http://www.tuicool.com/articles/3

BinaryFormatter:'test1' has now bin data= http://www.tuicool.com/articles/0b11
Failed to add: <__main__.BinaryFormatter object at 0x10277da90>
BinaryFormatter:'test1' has now bin data= http://www.tuicool.com/articles/0b10101
HexFormatter:'test1' has now hex data= http://www.tuicool.com/articles/0x15
DefaultFormatter:'test1' has data = http://www.tuicool.com/articles/21

BinaryFormatter:'test1' has now bin data= http://www.tuicool.com/articles/0b101000
DefaultFormatter:'test1' has data = http://www.tuicool.com/articles/40

BinaryFormatter:'test1' has now bin data= http://www.tuicool.com/articles/0b101000
Failed to add: <__main__.BinaryFormatter object at 0x10277da90>
Error: invalid literal for int() with base 10:'hello'
DefaultFormatter: 'test1' has data = http://www.tuicool.com/articles/40

BinaryFormatter:'test1' has now bin data= http://www.tuicool.com/articles/0b100
DefaultFormatter:'test1' has data = http://www.tuicool.com/articles/4

在輸出中我們看到,添加額外的觀察者,就會出現更多的輸出;一個觀察者被刪除後就不再被通知到。

總結

這一篇我們介紹了觀察者模式的原理以及 Python 代碼的實現。在實際的項目開發中,觀察者模式廣泛的運用於 GUI 編程,而且在仿真及服務器等其他時間處理架構中也能用到,比如: 數據庫觸發器Django 的信號系統Qt GUI 應用程序框架的信號(signal)與槽(slot)機智 以及 WebSocket 的許多用例。

參考鏈接

  • The 10 Minute Guide to the Observer Pattern in Python:http://www.giantflyingsaucer.com/blog/?p=5117

  • Observer:http://python-3-patterns-idioms-test.readthedocs.io/en/latest/Observer.html

最後,感謝女朋友支持。

歡迎關註(April_Louisa) 請我喝芬達
Tags: 格式化 觀察者 number self 一個 程序

文章來源:


ads
ads

相關文章
ads

相關文章

ad