设计模式-观察者模式

2,161 阅读6分钟

要解决的问题

生活中的一个场景:假设一份期刊,很多人都想看,但是这份期刊的出版时间不是固定的,有好内容的时候就出,没有规律。而这份期刊的读者又都想第一时间读到最新的内容,针对这一情况怎么处理,有两种方法:

第一种:很笨的方案,就是读者每天(甚至每天好几次)都向出版社询问是否出了新的期刊,如果出了的话,就直接买来读,没有的话,读者接着每天询问下去,这种方案,虽然能实现读者尽可能读到最新的期刊,但对于读者和出版社都是一个痛苦的过程。

第二种:就是我们本篇文章要讲到的观察者模式,要想读这份期刊的读者都去出版社订阅这份期刊,这样,到出版社出版新的期刊的时候,出版社根据自己的订阅表单查询订阅该期刊的读者,而后将新的期刊发送给读者。这样一来,读者和出版社都省了很多事情,还能高效地解决读者第一时间读到新期刊的需求。

将上述问题抽象地用编程的概念来描述就是:当一个对象(期刊)的状态发生改变(有新的期刊)的时候,如何让依赖于它的对象(读者)得到通知。这就是观察者模式要解决的问题。


模式定义

观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变的时候,所有依赖于它的对象都得到通知并被自动更新。

一个报纸/期刊对象,会有多个订阅者对象来订阅,当报纸出版的时候,也就是报纸对象状态发生改变的时候,需要通知所有的订阅者对象。观察者模式把这多个订阅者称为观察者:Observer,多个观察者观察的对象被称为目标:Subject。一个目标可以有多个观察者对象,一旦目标的状态发生了改变,所有注册的观察者都会得到通知,而后各个观察者会对通知做出相应的响应,执行相应的业务功能,并使自己的状态与目标对象的状态保持一致。

目标对象(Subject)的功能:

  • 一个目标对象可以被多个观察者观察。
  • 目标对象要提供观察者注册和退订。(不可能把让每个观察者对象去维护一套注册和退订的方案)
  • 当目标对象的状态发生变化时候,目标对象负责同志所有注册的、有效的观察者。

观察者(Observer)的功能:

  • 提供目标对象通知时对应的自己的更新方法。这个方法里可以将目标对象本身回调过来,也可以只将一些观察者需要的信息回调过来。

具体实现

首先,我们用AbstractSubject作为一个主题的基类,声明了主题应该具备的功能的接口,可以有默认的实现,也可以都让具体的主题子类去实现。用一个接口(iOS中叫做协议)来作为观察者,声明了作为观察者要实现的一个功能:主题状态变化时,对应自己应该有的更新操作,具体的观察者遵循这个接口(协议),实现自己的更新逻辑即可。

我们就以上边的报纸和读者的情景来实现:下边是UML图和具体的代码。

//=====================抽象主题类======================
@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地址


致谢:《研磨设计模式》