設計模式-觀察者模式

要解決的問題

生活中的一個場景:假設一份期刊,不少人都想看,可是這份期刊的出版時間不是固定的,有好內容的時候就出,沒有規律。而這份期刊的讀者又都想第一時間讀到最新的內容,針對這一狀況怎麼處理,有兩種方法:git

第一種:很笨的方案,就是讀者天天(甚至天天好幾回)都向出版社詢問是否出了新的期刊,若是出了的話,就直接買來讀,沒有的話,讀者接着天天詢問下去,這種方案,雖然能實現讀者儘量讀到最新的期刊,但對於讀者和出版社都是一個痛苦的過程。github

第二種:就是咱們本篇文章要講到的觀察者模式,要想讀這份期刊的讀者都去出版社訂閱這份期刊,這樣,到出版社出版新的期刊的時候,出版社根據本身的訂閱表單查詢訂閱該期刊的讀者,然後將新的期刊發送給讀者。這樣一來,讀者和出版社都省了不少事情,還能高效地解決讀者第一時間讀到新期刊的需求。編程

將上述問題抽象地用編程的概念來描述就是:當一個對象(期刊)的狀態發生改變(有新的期刊)的時候,如何讓依賴於它的對象(讀者)獲得通知。這就是觀察者模式要解決的問題。設計模式


模式定義

觀察者模式定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變的時候,全部依賴於它的對象都獲得通知並被自動更新。bash

一個報紙/期刊對象,會有多個訂閱者對象來訂閱,當報紙出版的時候,也就是報紙對象狀態發生改變的時候,須要通知全部的訂閱者對象。觀察者模式把這多個訂閱者稱爲觀察者:Observer,多個觀察者觀察的對象被稱爲目標:Subject。一個目標能夠有多個觀察者對象,一旦目標的狀態發生了改變,全部註冊的觀察者都會獲得通知,然後各個觀察者會對通知作出相應的響應,執行相應的業務功能,並使本身的狀態與目標對象的狀態保持一致。ui

目標對象(Subject)的功能:atom

  • 一個目標對象能夠被多個觀察者觀察。
  • 目標對象要提供觀察者註冊和退訂。(不可能把讓每一個觀察者對象去維護一套註冊和退訂的方案)
  • 當目標對象的狀態發生變化時候,目標對象負責同志全部註冊的、有效的觀察者。

觀察者(Observer)的功能:spa

  • 提供目標對象通知時對應的本身的更新方法。這個方法裏能夠將目標對象自己回調過來,也能夠只將一些觀察者須要的信息回調過來。

具體實現

首先,咱們用AbstractSubject做爲一個主題的基類,聲明瞭主題應該具有的功能的接口,能夠有默認的實現,也能夠都讓具體的主題子類去實現。用一個接口(iOS中叫作協議)來做爲觀察者,聲明瞭做爲觀察者要實現的一個功能:主題狀態變化時,對應本身應該有的更新操做,具體的觀察者遵循這個接口(協議),實現本身的更新邏輯便可。設計

咱們就以上邊的報紙和讀者的情景來實現:下邊是UML圖和具體的代碼。3d

//=====================抽象主題類======================
@interface AbstractSubject : NSObject
@property (nonatomic,strong)NSMutableArray <id<ObserverProtocol>> *observers;
/**
註冊觀察者
@param observer 遵循ObserverProtocol協議的觀察者對象
*/
- (void)registerObserver:(id<ObserverProtocol>)observer;
/**
移除觀察者
@param observer 遵循ObserverProtocol協議的觀察者對象
*/
- (void)removeObserver:(id<ObserverProtocol>)observer;
/**
通知全部的觀察者
*/
- (void)notifyObservers;
@end
@implementation AbstractSubject
@end
//===========================觀察者接口(協議)=====================
@protocol ObserverProtocol <NSObject>
/**
主題狀態更新後,獲得通知,進而作出對應的響應
@param subject 主題經過通知傳給過來的信息,這裏將主題本身傳了過來,也能夠只是傳遞一些觀察者須要的信息。
*/
- (void)update:(AbstractSubject *)subject;
@end

//******************************具體主題類*********************
@interface NewsPaper : AbstractSubject//繼承於抽象主題類
@property (nonatomic,copy)NSString *content;//加上了本身的屬性
@end
//----------------------------------實現了做爲主題應該有的功能
@implementation NewsPaper
- (instancetype)init
{
   self = [super init];
   if (self) {
       self.observers = [NSMutableArray array];
   }
   return self;
}
- (void)registerObserver:(id<ObserverProtocol>)observer
{
   if ([self.observers containsObject:observer]) {
       return;
   }
   [self.observers addObject:observer];
}
- (void)removeObserver:(id<ObserverProtocol>)observer
{
   [self.observers removeObject:observer];
}
- (void)notifyObservers
{
   for (id<ObserverProtocol> observer in self.observers) {
       [observer update:self];
   }
}
- (void)setContent:(NSString *)content
{
   _content = content;//這裏必定要先更新數據,再將通知發送出去。
   [self notifyObservers];
}
@end
//******************************具體觀察者類*******************
@interface Readers : NSObject<ObserverProtocol>//遵循觀察者協議
- (instancetype)initWithName:(NSString *)name;
@property (nonatomic,copy)NSString *name;
@end
//-----------------------實現對應的更新方法
@implementation Readers
- (instancetype)initWithName:(NSString *)name
{
   if (self = [super init]) {
       _name = name;
   }
   return self;
}
- (void)update:(AbstractSubject *)subject
{
   if ([subject isKindOfClass:[NewsPaper class]]) {
       NewsPaper *paper = (NewsPaper *)subject;
       NSLog(@"%@開始讀本期報紙:%@",self.name,paper.content);
   }
}
@end
複製代碼

總結

  • 目標和觀察者之間是一對多關係(據需求不一樣,也能夠一個目標只有一個觀察者)。一個觀察者也能夠有不一樣多個觀察的目標,可是最好把不一樣目標狀態的變化對應本身的更新處理分紅不一樣的方法來實現,不容易混淆。
  • 觀察者模式中,觀察者和目標是單向依賴的,觀察者依賴於目標,而目標不會依賴於觀察者【讀者依賴於期刊報社,報社是不會依賴某個讀者的】。聯繫的主動權在目標手中,只能是目標去主動通知,而觀察者只能是被動等待接收通知。
  • 觀察者模式的本質是觸發聯動:當修改目標對象的狀態的時候,就會觸發相應的通知,而後會循環遍歷全部註冊的觀察者對象的相應方法,聯動到觀察者的變化。

注意: (1)目標發送通知的時機,通常狀況下,要在目標本身更新完狀態後,再將通知發送出去,不然,可能觀察者接收到了通知,可是目標自己的狀態尚未發生更新,就出問題了。(2)避免相互觀察的狀況:假如在一套觀察者模式中:A和B對象觀察C對象C做爲主題;在另外一套觀察者模式中:C和D對象觀察A對象A做爲主題,這樣,A和C就會相互觀察,相互聯動起來,就有可能出現死循環了。

以上做爲筆者本身的讀書筆記,若有理解錯誤的地方,還請指出。謝謝!

Demo地址


致謝:《研磨設計模式》

相關文章
相關標籤/搜索