天天看點

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

我們不僅要知其然,還要知其是以然。今天部落格的主題是“觀察者模式”(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)這三個方法。Boss是SubjectType的子類,繼承了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架構中的通知

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(類.方法),如下所示:

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

如果你對本篇部落格的内容從頭到尾的進行閱讀,并且将上面的執行個體用自己熟悉的一門語言來實作的話,想必你對“觀察者模式”更進一步的了解了吧。