設計模式(二):自己動手使用“觀察者模式”實現通知機制


在之前發布Objective-C系列博客的時候,其中提到過OC的通知機制,請參考《Objective-C中的老板是這樣發通知的(Notification)》這篇博客。在之前關於Notification的博客中,只介紹了Foundation框架中的通知的使用方式。正如前面博客中提到的那樣,通知是“一對多的關系”,類似於廣播。一個人發通知,多個人接收。這也就是設計模式中的“觀察者模式”。接收者的一方是Observer(觀察者),而發送方是Subject(主題)。一個人要想成為Observer,要在Subject中進行注冊,也就是說要給Subject說,我要成為你的觀察者,然后Subject就會給Observer推送消息。

我們不僅要知其然,還要知其所以然。今天博客的主題是“觀察者模式”(Observe Pattern),所以我們要先通過一個小的Demo來理解一下“觀察者模式” ,當然使用的是Swift語言來實現的(語言只是載體呢,主要還是模式不是)。通過一個小Demo對“觀察者模式”進行學習后,緊接着會看一下在Swift中是如何使用Foundation框架中的通知的,並給出相應的示例。最后就是我們放大招的時候了,我們會參照着Foundation框架中的通知機制來實現我們自己的“通知中心”,說白了,就是我們不用Foundation的通知機制,我們自己寫,但是使用方式與Foundation框架中的通知機制幾乎相同。這應該就是Foundation框架中通知機制的實現原理吧。在本博文的開頭需要有個干貨預警呢。

 

一、認識“觀察者模式”(Observe Pattern)

1.觀察者模式的定義

開門見山,先來看一下觀察者模式的定義吧:

觀察者設計模式定義了對象間的一種一對多的依賴關系,以便一個對象的狀態發生變化時,所有依賴於它的對象都得到通知並自動刷新。

上面就是觀察者模式的定義。也許你看定義有些抽象,其實觀察者模式並不難理解。舉個栗子🌰,比如老板在一個辦公室里開會,辦公室里有部分員工,在辦公室的員工就是Observer(觀察者),正在開會的老板就是Subject(主題:負責發送通知---Post Notification)。如果其他員工也想成為Observer,那么必須得進入(addObserver)正在開會的會議室成為觀察者。員工成功觀察者后收到通知得做一些事情吧(doSomething),比如記個筆記神馬的。如果此時員工鬧情緒,不想聽老板開會了,於是通過removeObserver走出了會議室。上面這個過程其實就是觀察者模式。

 

2.從一個示例來認識“觀察者模式”

上面描述了發通知的老板和接收通知的員工的觀察者模式。接下來我們要用一個完整的示例來描述這個通知的過程,從一個完整的示例中來觀察一下“觀察者模式”的運作方式。當然場景還是使用Boss發送通知,員工接收通知的場景。這顯然就是一對多的關系。

了解設計模式怎么會沒有“類圖”呢,當然在本篇博客以及本系列博客中使用的“類圖”並不是真正的類圖,只是看起來像類圖,也就是類"類圖"。但是類“類圖”足以表示類間的各種關系。下方就是我們將要實現的“類圖”。當然下方的的結構有很大的重構空間的,下方的基類完全可以使用protocol來實現的,但是為了簡化結構我們用了簡單的繼承。但是下方示例是完全可以來表示“觀察者模式”的。因為今天我們的主題是“設計模式”,其他關於重構的問題我們先不予理會。

下方SubjectType的基類就是通知者基類,負責發通知的,其中有表示發布消息的info: String字段,以及保存多個觀察者的observerArray的數組(因為Subject :Observers 是1 對多的關系,我們在這兒使用數組類存儲Observers)。在SubjectType類中還有三個方法,簡單的說就是注冊觀察者(registerObserver)、移除觀察者(removeObserver)、通知觀察者(notifyObserver)這三個方法。BossSubjectType的子類,繼承了SubjectType的所有屬性以及要重寫SubjectType中的三個方法,來完整要做的事情。在Boss中還有setInfo()方法,負責在更新Info信息的時候調用發出通知的方法。

ObserverType是觀察者的基類,其中的info:String字段來存儲接收到的通知信息。udpate()方法就是接收到通知后要執行的方法,也就是說Boss一發通知,員工就會執行update()方法,而其中的display()方法就是對上述信息進行輸出。當然把SubjectType以及ObserverType做成基類,不利於我們后期的擴展或者在后期擴展中會產生重復的代碼,使用使用接口或者結合者其他的設計模式可以很好的解決該問題。不過這不在於今天我們這篇博客的討論范圍之內,我們今天的重點是“觀察者模式”

    

3.上述“類圖”的具體實現

原理在上述類“類圖”中說的很明白了,接下來我們要通過上面的介紹來開始編寫我們的代碼,去實現上述“觀察者模式”。上面的Boss負責發通知,Coder和PM負責監聽Boss發的通知。下方就是我們的具體實現。

(1)ObserverType與SubjectType基類的實現如下圖所示,這兩個基類中的內容與上述“類圖”中的描述一致。在SubjectType基類中的observesArray中存儲的是ObserverType類型(包括其子類)的對象,也就是所有的觀察者。

  

(2)下方就是我們負責發通知的大Boss。Boss繼承自SubjectType,當Boss執行setInfo()方法時(也就是修改info的值時)就會調用notifyObservers()進行通知的發送。在Boss中的registerObserver()方法用來添加監聽者(為了防止重復添加,我們在添加前先進行移除),removeObserver()則是負責移除監聽者,notifyObservers()是發送通知並調用觀察者相應的方法。具體實現如下所示:

    

(3)下方實現的是兩個觀察者,分別是Coder(程序員)和PM(產品經理)。這兩者都是ObserverType基類的子類,重寫了ObserverType的update()和display()方法。觀察者在觀察到Subject的info被改變后,就會執行其中的update()方法。Coder和PM類的具體實現如下所示。

   

(4)經過上面這三小步,我們的Demo就實現完了,該到了我們測試的時候了,下方是測試用例以及輸出結果。從輸出結果我們不難看出,第一次發通知的時候,Coder和PM都接收到了通知,因為他們倆都是“觀察者”。緊接着我們移除了Coder觀察者,在發送第二次通知的時候,因為現在Coder不再是觀察者了,所以第二次發送通知只有PM能收到。具體如下所示。

   

 

二、Foundation框架中的通知

在Foundation框架中是有一套完整的“觀察者模式”機制的,也就是“通知機制”。在《Objective-C中的老板是這樣發通知的(Notification)》這篇博客中就介紹了Foundation框架中的通知機制。本篇博客的第二部分就回顧一下Foundation框架中的“通知機制”,當然在本篇博客中我們會使用Swift來實現Foundation框架中的通知機制。

1. 簡述NotificationCenter

在Foundation框架中的通知機制中有通知中心(NotificationCenter)這個概念,通知中心扮演者調度通知的作用。Subject往通知中心發送通知,由通知中心進行統一管理,把該Subject發送的消息分發給相應的觀察者。可以這么說,通知中心是一個大集合,集合中有多個Subject和多個Observe的集合。而通知中心扮演的角色就是講Subject與相應的Observer進行關聯。下方就是簡單的原理圖了。

  

2.Foundation框架中的通知的使用

(1)創建Subject

Foundation中自帶的通知機制使用起來比較簡單的,我們暫且將發送消息的稱為Subject,通知的觀察者稱為Observer。下方是通知的Subject的實現,下方的Boss扮演的就是Subject角色。如果Boss要發送通知的話,需要下方幾部:

I. 創建消息字典,該字典承載的就是觀察着說獲取的信息。

II. 創建通知(NSNotification),該通知也是要發送給Observer的。通知中的信息量更大啊,其中包括發出通知的Subject的名字(每個Subject都有一個名字),還包括發送通知的對象,以及我們創建的消息字典。

III. 將該通知發送給“通知中心”----NotificationCenter,NotificationCenter會根據Notification所承載的信息來找到觀察此通知的所有Observers,並把該Notification傳給每個觀察者。

下方就是Subject發送通知的具體做法。

   

(2)添加Observer

上面這一步是創建Subject,也就是往“通知中心”發送通知。接下來就是要往“通知中心”添加Observer,下方的代碼就是往“通知中心”添加Observer。在添加Observer是,我們要指定該觀察者所觀察的是哪一個Subject。這也就是為什么要為Subject命名了,在添加Observer時就是通過Subject的名字來指定其觀察的對象的。除了指定觀察對象外,還需要指定收到通知后所執行的方法。在指定的方法中需要有一個參數,該參數就是用來接收上方Subject所發出的NSNotification的對象的。Observe的具體實現方式如下所示。

有一點需要注意的是,在當前對象釋放時要移除觀察者。

   

(3)測試用例

經過上面的兩步,我們就已經使用Foundation框架中的通知機制將Subject和Observers進行了關聯。接下來我們將對上方的代碼進行測試,下方是我們的測試用例。測試用例灰常的簡單了,在此就不做過多的贅述了。

   

 

三、照貓畫虎:自定義通知中心

經過上面的部分,想必應該對“觀察者模式”有所了解吧。經過上面的第二部分,你多Foundation中的通知機制使用是沒有太大問題的。但是僅僅會使用不是我們想要的,還是那句話,要知其所以然。接下來我們就“照貓畫虎,比葫蘆畫瓢”,自己實現一套專屬自己的通知機制。在我們接下來要實現的通知機制中我們要根據Foundation框架中通知調用方式,來實現我們自己的通知。自定義通知的調用方式我們要做到與Foundation框架中的通知的使用方式一致,但是我們的命名是不同的。這部分才是今天博客的大招。

1.原理分析

我們先對Foundation框架中的通知機制進行觀察,找一些靈感。當然我們看不到Foundation框架的源碼,但是我們可以通過其對外暴露的接口來猜測其中通知的實現機制。下方是我們經過分析然后在經過推敲畫出來的我們將要自己實現的通知機制的“類圖”。我們也將根據下方的類圖來實現屬於我們自己的通知機制,“類圖”如下。

下圖中的MyCustomNotificationCenter就對應的NSNotificationCenter,  MyCustomNotification則對應着NSNotification,而下方的MyObserver類與MySubject類在Foundation中對外應該是不可見的(這是個人猜測了),這兩個類是為了實現該通知機制所創建的Subject和Observer。下方“通知機制”的運作方式就是Boss將Notification發送到NotificationCenter,然后NotificationCenter在通過其內部實現機制,將Boss發送過來的Notification發送到Coder。

在MyCustomNotification這個通知載體類中(類比NSNotification)的name字段表示發送通知的對象的名稱,也就是上面的“Boss”, object字段就指的是上述示例的Boss的對象,userInfo就代表着發送給Observer的信息字典。MyObserver中存儲的就是觀察者對象(observe)和觀察者對象收到通知后要執行的方法(selector)。

MySubject類扮演者“觀察者模式”中的Subject,其中的notification字段記錄着要發送的通知,其類型是MyCustomNotification。MySubject類中的observers是一個數組,其中存儲的是該Subject對應的所有觀察者。其中還分別有添加觀察者(addCoustomObserver()), 移除觀察者(removeCustomObserver()), 發送通知(postNotification())方法。具體如下“類圖”所示。

中間的紅框中的MyCustomNotificationCenter類,就是通知中心了(類比NSNotificationCenter), 該類的對象是通過defaultCenter()方法獲取的單例。在該方法中有一個名為center的字段,center字段是字典類型,該字典的Key是我們為MySubject對象指定的name, Value是MySubject對象。其中也有移除、添加觀察者,發送通知等方法。

    

2、Subject與Observer的代碼實現

上面的原理也扯的夠多了,接下來我們要根據上面的描述來使用Swift語言進行代碼實現。還是直接上代碼來的直觀。在實現代碼之前有一點需要聲明的就是,該示例不能在Playground中實現,因為在Playground中執行performSelector()方法會拋出異常,所以我們需要在真正的工程中去實現(如果想簡單一些,可以創建一個控制台程序來進行測試)。

(1). MyCustomNotification(類比NSNotification)具體實現

下方代碼就是MyCustomNotification的具體實現了。通過下方的具體代碼不難看出,name字段表示發送通知的對象的名稱,也就是上面的“Boss”, object字段就指的是上述示例的Boss的對象,userInfo就代表着發送給Observer的信息字典。該類比較簡單就不做過多贅述了。

  

(2). MyObserver的具體實現

下方代碼就是MyObserver類的具體實現,該類還是比較簡單的。MyObserver中存儲的就是觀察者對象(observer)和觀察者對象收到通知后要執行的方法(selector)。當收到通知時,就會執行observer的selector方法。

    

 

(3). MySubject的實現

緊接着要實現我們的MySubject類了,MySubject類將Notification與Observers進行關聯。具體說來就是當MySubject收到Notification中,就會遍歷其所有的觀察者(observers的類型是ObserveArray,其中存儲的是MyObserver的對象),遍歷觀察者時就會去執行該觀察者所對應的selector方法。下方的notification存儲的就是Subject所要發出的通知。observers字段是數組類型,其中存儲的是MyObserver的對象。addCustomObserver()方法其實就是往observers數組中添加觀察者,而

removeCustomObserver()方法則是移除observers數組中的觀察者。postNotification()方法的功能則是對observers數組進行遍歷取出MyObserver的對象,然后執行該對象中的selector方法,並且將notification作為selector方法的參數。具體實現如下所示。

   

3.“通知中心”的代碼實現

上面實現的是Notification、Subject以及Observer的代碼的實現,接下來要實現“通知中心”模塊。因為該模塊的代碼比較多,業務邏輯相對復雜,所以我想把這部分代碼進行拆分,然后各個擊破。下方截圖是MyCustomNotificationCenter類的定義,我們先將類中的代碼折疊,然后將折疊的代碼進行拆分各個擊破。下方是通知中心MyCustomNotificationCenter類的定義方式。

   

 

(1)在MyCustomNotificationCenter類中我們也模擬NSNotificationCenter的defaultCenter()方法來獲取該類的單例,具體代碼如下所示。下方我們將其構造器聲明為私有,防止其在外部進行實例化。然后使用靜態方法defaultCenter()來返回一個當前類的靜態實例,下方就是Swift中比較簡單的“單例模式”了。

   

(2)、下方的的方法就是通知中心發送通知的過程了,對應着NSNotificationCenter.defaultCenter()中的postNotification(notifaction)。我們要實現postNotification()方法也有一個參數,該參數就是Subject要發送的通知。在postNotification()方法中,首先會調用getSubjectWithNotifaction(notification)方法來從center中獲取可以發送該notification的Subject對象。在getSubjectWithNotifaction(notification)中,如果center中沒有可以發送該notification的對象,那么就創建一個MySubject對象,並將該notification賦值給這個新建的MySubject對象,最后將我們創建的這個新的subject添加進center數組中。然后調用該subject對象的postNotification()方法即可,具體實現如下所示。

   

(3)下方代碼就是添加監聽着了,與NSNotificationCenter.defaultCenter()中的addObserver()方法相對應。首先我們把傳入的參數生成MyObserver的對象,然后通過aName從center字典中獲取相應的MySubject對象。如果center中沒有對應的MySubject對象,我們就創建該對象,並且將該對象的notification屬性暫且指定為nil。最后調用MySubject類中的addCustomObserver()方法進行觀察者的添加。

   

(4) 下方代碼就比較簡單了,就是移除觀察者。首先也是通過name從center字典中獲取MySubject的對象,然后調用MySubject對象的removeCustomObserver()方法進行移除掉。具體代碼如下所示。

   

 

4.測試用例

經過上面的艱苦跋涉,我們自己定義的通知機制終於完成了。下方就是我們為上述自定義通知機制所創建的測試用例。將下方的測試用例與Foundation框架中的通知機制的測試用例(本篇博客第二部分)相比是非常相似的。至此我們自定義的通知就Over了,這也就是Foundation框架中通知機制實現的大概原理吧,當然Foundation框架還對其做了各種優化。但是萬變不離其宗,都是“觀察者模式”的應用。

下方是我們自定義通知的測試用例,是在本篇博客中第二部分的代碼的基礎上進行修改單,就是Foundation框架中的通知進行了替換。具體如下所示:

上面是在Swift2.1版本中實現的代碼,在Swift2.2中的Selector的參數有所變化,在此還是需要說明一下的,aSelector參數在Swift2.2中得使用#selector(類.方法),如下所示:

 

如果你對本篇博客的內容從頭到尾的進行閱讀,並且將上面的實例用自己熟悉的一門語言來實現的話,想必你對“觀察者模式”更進一步的了解了吧。

同樣,在本篇博客的末尾,我們給出類本篇博客是Dmeo, Github: https://github.com/lizelu/DesignPatterns-Swift


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM