觀察者模式與依賴反轉原則

最近寫了一個“休息吧,程序員”的小工具,用到了觀察者模式,感覺自己的理解還不夠深入,藉此機會稍微深入學習一下。

項目介紹:提醒程序員不要過度工作的一個腳本,暖心程序跟你道晚安,使用觀察者模式實現,我把它叫做:休息吧,程序員!

po圖

項目地址:https://github.com/cooljacket/relax_please
先po一下我實現出來的效果圖(注意桌面右上角):
這裏寫圖片描述
暖心跟你道晚安~

這裏寫圖片描述
如老媽一般的監督你不要過勞!

項目設計

首先說下這個項目的組成:
1. 監聽器:監聽系統的鍵盤輸入、系統時間
2. 發送提醒:一旦監聽的事件發生(比如超過11點,該睡覺了),那麼監聽器就會觸發這個“發送提醒器”,提醒器會以某種方式發送通知給使用者

這樣看來,這個項目的對象關係是一個很經典的“觀察者模式”的模型,下面先看一下我畫的UML圖:
觀察者模式

看不懂UML圖的朋友也沒關係,稍微解釋一下就知道了:
1. 監聽器抽象接口Listener,它實現了所有監聽器共有的功能,即綁定觀察者,解綁觀察者,(在所監聽的事件發生的時候)通知所有綁定的觀察者,以及監聽事件。
2. 我需要監聽兩個事件,一個是使用電腦的情況(通過鍵盤輸入來估計),一個是當前的工作時間(看看是否有熬夜),所以繼承實現了兩個具體的監聽器,KeyBoardListener和SayGoodNightListener,它們都只需要實現listening函數即可,因爲其它的都在抽象類Listener中實現好了!
3. 觀察者抽象類Observer,這個其實只是個接口而已,不是一個類,因爲update()雖然是共有的接口函數,但它的實體是因人而異的,沒法在這個類裏給出一個default的實現(對比一下Listener的notify方法就知道了)。
4. 目前的發通知給使用者,只實現了一種方式:發送泡泡彈窗到桌面右上角,所以只寫了一個NotifySendObserver類。

值得注意的是:一個監聽器可以有多個觀察者,而一般一個觀察者只能有一個監聽器。比如監聽是否熬夜這個事件,可以有多種通知方式,比如響鬧鈴,發送泡泡彈窗到桌面的右上角,記錄熬夜情況到日誌文件以備後續分析,等等。而一般一個觀察者都是特化了的,不會用在多個監聽器上面,比如你熬夜的時候響一段“睡吧睡吧我親愛滴寶貝……”的搖籃曲,是針對特定的這個(熬夜)事件的!

什麼是觀察者模式?

引用一段話1來描述:

建立一種對象與對象之間的依賴關係,一個對象發生改變時將自動通知其他對象,其他對象將相應做出反應。在此,發生改變的對象稱爲觀察目標,而被通知的對象稱爲觀察者,一個觀察目標可以對應多個觀察者,而且這些觀察者之間沒有相互聯繫,可以根據需要增加和刪除觀察者,使得系統更易於擴展,這就是觀察者模式的模式動機。

注意這裏,觀察者並不是真的去觀察,而是被觀察目標通知纔會得知有事件發生的!

這是一個事件驅動模型,不直接想它的好處,先反過來想,如果不這樣做,而是讓NotifySendObserver主動去詢問/檢測事件有無發生,這樣的話,得設置好去檢測的時間間隔,除非不停去檢測,否則肯定是有概率會錯過一些事件的。忙死忙活,喫力不討好^_^(手動微笑)。

而觀察者模式正是把檢測事件和響應事件這兩個職責給分離了,各司其職。我這邊自己監視使用者有沒有熬夜,有的話,我就通過接口告訴你,你再發通知給使用者,叫ta趕緊睡覺。沒有發通知給你,你也不用來問我,就是沒事情發生,各自清閒~

再舉個簡單的例子,就是我們平時按手機app裏的按鈕,那個應用內部肯定不會時刻去檢測這個按鈕有沒有被按下,而是按鈕被按下的時候,系統會發送一個消息給應用去處理。

Why抽象接口?

有人可能會說,哎,你怎麼要搞這麼麻煩啊,無論是監聽器,還是觀察者,都要抽象出一個接口出來?

這裏安利一個我自己到現在還沒懂透的“面向對象五大原則SOLID”:
這裏寫圖片描述
參考維基百科:https://zh.wikipedia.org/wiki/SOLID_(%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E8%AE%BE%E8%AE%A1)

這裏很明顯運用的就是D,依賴反轉原則。

問題還是回來了,用了有什麼好處呢?不用,又有什麼壞處呢?

這裏推薦一篇關於依賴倒轉原則的好文章,裏邊的“媽媽講故事”的例子很鮮明地體現了這個原則的好處:高層模塊可以自由選擇底層模塊,耦合性很低。

說人話,就是,在這個例子中,如果我不抽象一個Observer接口的話,那麼Listener(高層模塊)就直接依賴NotifySendObserver(底層模塊)了,那如果我後續不想用這種通知方式,而是想改用音樂作爲鬧鈴,是不是除了寫多一個MusicObserver類,還要改動Listener類呢?

有人可能會說,這在python裏都一樣,因爲每次運行都是重新編譯,那試問一下,如果你是公開發布的一個東西,還能這麼做嗎?如果不是用python而是C++、Java之類的靜態語言呢?

嗯,抽象接口的好處就是這樣了。

而Listener作爲一個抽象類,則不是同樣的想法了。我想實現多個監聽器類,它們有很鮮明的公共邏輯,比如上面的KeyBoardListener和SayGoodNightListener,那這個時候主要是繼承重用的想法,而不是爲了要依賴反轉。

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