官术网_书友最值得收藏!

第1章 監聽模式

1.1 從生活中領悟監聽模式

1.1.1 故事劇情—幻想中的智能熱水器

剛剛大學畢業的 Tony只身來到北京這個大城市,開始了北漂生活。但剛剛畢業的他身無絕技、包無分文,為了生活只能住在沙河鎮一個偏僻的村子里,每天坐著程序員專線(13號線)穿梭于昌平區與西城區……

在寒冷的冬天,Tony坐2個小時的“地鐵+公交”回到住處,拖著疲憊的身體,準備洗一個熱水澡暖暖身體,奈何簡陋的房子中用的還是20世紀90年代的熱水器。因為熱水器沒有警報,更沒有自動切換模式的功能,所以燒熱水必須得守著,不然時間長了成“殺豬燙”,時間短了又“冷成狗”。無奈的 Tony 背靠著墻,頭望著天花板,深夜中做起了白日夢:一定要努力工作,過兩個月我就可以自己買一個智能熱水器了,水燒好了就發一個警報,我就可以直接去洗澡。還要能自己設定模式,既可以燒開了用來喝,又可以燒暖了用來洗澡……

1.1.2 用程序來模擬生活

Tony陷入白日夢中……他的夢雖然不能在現實世界中立即實現,但在程序世界里可以。程序來源于生活,下面我們就用代碼來模擬Tony的白日夢。

源碼示例1-1 模擬故事劇情

測試代碼:

輸出結果:

1.2 從劇情中思考監聽模式

這個代碼非常簡單,水溫在50℃~70℃時,會發出警告:可以用來洗澡了!水溫在100℃時也會發出警告:可以用來飲用了!在這里洗澡模式和飲用模式扮演了監聽的角色,而熱水器則是被監聽的對象。一旦熱水器中的水溫度發生變化,監聽者就能及時知道并做出相應的判斷和動作。這就是程序設計中監聽模式的生動展現。

1.2.1 什么是監聽模式

Define a one-to-many dependency between objects so that when one object changes state,all its dependents are notified and updated automatically.

在對象間定義一種一對多的依賴關系,當這個對象狀態發生改變時,所有依賴它的對象都會被通知并自動更新。

監聽模式是一種一對多的關系,可以有任意個(一個或多個)觀察者對象同時監聽某一個對象。監聽的對象叫觀察者(后面提到監聽者,其實就指觀察者,兩者是相同的),被監聽的對象叫被觀察者(Observable,也叫主題,即Subject)。被觀察者對象在狀態或內容(數據)發生變化時,會通知所有觀察者對象,使它們能夠做出相應的變化(如自動更新自己的信息)。

1.2.2 監聽模式設計思想

監聽模式又名觀察者模式,顧名思義就是觀察與被觀察的關系。比如你在燒開水的時候看著它開沒開,你就是觀察者,水就是被觀察者;再比如你在帶小孩,你關注他是不是餓了,是不是渴了,是不是撒尿了,你就是觀察者,小孩就是被觀察者。觀察者模式是對象的行為模式,又叫發布/訂閱(Publish/Subscribe)模式、模型/視圖(Model/View)模式、源/監聽器(Source/Listener)模式或從屬者(Dependents)模式。當你看這些模式的時候,不要覺得陌生,它們就是監聽模式。

監聽模式的核心思想就是在被觀察者與觀察者之間建立一種自動觸發的關系。

1.3 監聽模式的模型抽象

1.3.1 代碼框架

模擬故事劇情的代碼(源碼示例1-1)還是相對比較粗糙的,我們可以對它進行進一步的重構和優化,抽象出監聽模式的框架模型。

源碼示例1-2 監聽模式的框架模型

1.3.2 類圖

上面的代碼框架可用圖表示,如圖1-1所示。

Observable是被觀察者的抽象類,Observer是觀察者的抽象類。addObserver、removeObserver分別用于添加和刪除觀察者,notifyObservers 用于內容或狀態變化時通知所有的觀察者。因為Observable的notifyObservers會調用Observer的update方法,所有觀察者不需要關心被觀察的對象什么時候會發生變化,只要有變化就會自動調用update,所以只需要關注update實現就可以了。

圖1-1 監聽模式的類圖

1.3.3 基于框架的實現

有了源碼示例1-2的代碼框架之后,我們要實現示例代碼的功能就更簡單了。我們假設最開始的示例代碼為Version 1.0,下面看看基于框架的Version 2.0吧。

源碼示例1-3 Version 2.0的實現

測試代碼不用變,讀者可以自己跑一下,會發現輸出結果和之前的是一樣的。

1.3.4 模型說明

1.設計要點

在設計監聽模式的程序時要注意以下幾點。

(1)要明確誰是觀察者誰是被觀察者,只要明白誰是應該關注的對象,問題也就明白了。一般觀察者與被觀察者之間是多對一的關系,一個被觀察對象可以有多個監聽對象(觀察者)。如一個編輯框,有鼠標點擊的監聽者,也有鍵盤的監聽者,還有內容改變的監聽者。

(2)Observable 在發送廣播通知的時候,無須指定具體的 Observer,Observer 可以自己決定是否訂閱Subject的通知。

(3)被觀察者至少需要有三個方法:添加監聽者、移除監聽者、通知Observer的方法。觀察者至少要有一個方法:更新方法,即更新當前的內容,做出相應的處理。

(4)添加監聽者和移除監聽者在不同的模型稱謂中可能會有不同命名,如在觀察者模型中一般是addObserver/removeObserver;在源/監聽器(Source/Listener)模型中一般是attach/detach,應用在桌面編程的窗口中還可能是attachWindow/detachWindow或Register/UnRegister。不要被名稱弄迷糊了,不管它們是什么名稱,其實功能都是一樣的,就是添加或刪除觀察者。

2.推模型和拉模型

監聽模式根據其側重的功能還可以分為推模型和拉模型。

推模型:被觀察者對象向觀察者推送主題的詳細信息,不管觀察者是否需要,推送的信息通常是主題對象的全部或部分數據。一般在這種模型的實現中,會把被觀察者對象中的全部或部分信息通過update參數傳遞給觀察者(update(Object obj),通過obj參數傳遞)。

如某App的服務要在凌晨1:00開始進行維護,1:00—2:00所有服務會暫停,這里你就需要向所有的App客戶端推送完整的通知消息:“本服務將在凌晨1:00開始進行維護,1:00—2:00所有服務會暫停,感謝您的理解和支持!”不管用戶想不想知道,也不管用戶會不會在這期間訪問App,消息都需要被準確無誤地發送到。這就是典型的推模型的應用。

拉模型:被觀察者在通知觀察者的時候,只傳遞少量信息。如果觀察者需要更具體的信息,由觀察者主動到被觀察者對象中獲取,相當于觀察者從被觀察者對象中拉數據。一般在這種模型的實現中,會把被觀察者對象自身通過 update 方法傳遞給觀察者(update(Observable observable),通過observable 參數傳遞),這樣在觀察者需要獲取數據的時候,就可以通過這個引用來獲取了。

如某App有新的版本推出,需要發送一個版本升級的通知消息,而這個通知消息只會簡單地列出版本號和下載地址,如果需要升級App,還需要調用下載接口去下載安裝包完成升級。這其實也可以理解成拉模型。

推模型和拉模型其實更多的是語義和邏輯上的區別。我們前面的代碼框架,從接口[update(self,observer,object)]上你應該可以知道是同時支持推模型和拉模型的。作為推模型時,observer可以傳空,推送的信息全部通過object傳遞;作為拉模型時,observer和object都傳遞數據,或只傳遞observer,需要更具體的信息時通過observer引用去取數據。

1.4 實戰應用

在互聯網廣泛普及和快速發展的時代,信息安全被越來越多的人重視,其中賬戶安全是信息安全最重要的一個部分。很多網站都會有一個賬號異常登錄檢測和診斷機制。當賬戶異常登錄時,會以短信或郵件的方式將登錄信息(登錄的時間、地區、IP地址等)發送給已經綁定的手機或郵箱。

登錄異常其實就是登錄狀態的改變。服務器會記錄你最近幾次登錄的時間、地區、IP地址,從而得知你常用的登錄地區;如果哪次檢測到你登錄的地區與常用登錄地區相差非常大(說明是登錄地區的改變),則認為是一次異常登錄。而短信和郵箱的發送機制我們可以認為是登錄的監聽者,只要登錄異常一出現就自動發送信息。

邏輯分析清楚之后就可以設計我們的代碼了,首先設計類圖,如圖1-2所示。

圖1-2 登錄異常檢測機制的設計類圖

源碼示例1-4 登錄異常的檢測與提醒

測試代碼:

輸出結果:

在實際的項目中,用戶信息(如用戶名、密碼)都是放在數據庫中的,登錄時還要進行用戶信息的校驗;用戶最近幾次的登錄信息也存在數據庫中。這里,為模擬程序簡單起見,省去了數據庫操作這一步,而且只記錄上一次的登錄信息到Account對象中。

1.5 應用場景

(1)對一個對象狀態或數據的更新需要其他對象同步更新,或者一個對象的更新需要依賴另一個對象的更新。

(2)對象僅需要將自己的更新通知給其他對象而不需要知道其他對象的細節,如消息推送。

學習設計模式,更應該領悟其設計思想,不應該局限于代碼的層面。監聽模式還可以用于網絡中的客戶端和服務器,比如手機中的各種App的消息推送,服務端是被觀察者,各個手機App 是觀察者,一旦服務器上的數據(如 App 升級信息)有更新,就會被推送到手機客戶端。在這個應用中你會發現服務器代碼和App客戶端代碼其實是兩套完全不一樣的代碼,它們是通過網絡接口進行通信的,所以如果你只停留在代碼層面是無法理解的!

主站蜘蛛池模板: 霍州市| 临湘市| 明溪县| 兴和县| 大洼县| 扬州市| 云林县| 揭阳市| 烟台市| 扎兰屯市| 阿拉善盟| 开封市| 微博| 香河县| 年辖:市辖区| 登封市| 衡阳市| 农安县| 垫江县| 榕江县| 楚雄市| 略阳县| 察隅县| 平武县| 钦州市| 永顺县| 交口县| 呼图壁县| 日照市| 郎溪县| 驻马店市| 当阳市| 汤原县| 石首市| 天等县| 五峰| 怀柔区| 黄平县| 墨脱县| 临朐县| 玉林市|