定义
观察者模式(Observer Pattern):定义对象间的一种一对多的依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象都可以到通知并做相应针对性的处理。
适用场景
凡是涉及到一对一或者一对多的对象交互场景都可以使用观察者模式。通常我们使用观察者模式实现一个对象的改变会令其他一个或多个对象发生改变的需求,比如换肤功能,监听列表滚动的偏移量等等。
现在我们清楚了观察者模式的适用场景,下面看一下观察者模式的成员和类图。
成员与类图
成员
观察者模式有四个成员:
- 目标(Subject):目标是被观察的角色,声明添加和删除观察者以及通知观察者的接口。
- 具体目标(Concrete Subject):具体目标实现目标类声明的接口,保存所有观察者的实例(通过集合的形式)。在被观察的状态发生变化时,给所有登记过的观察者发送通知。
- 观察者(Observer):观察者定义具体观察者的更新接口,以便在收到通知时实现一些操作。
- 具体观察者(Concrete Observer):具体观察者实现抽象观察者定义的更新接口。
下面通过类图来看一下各个成员之间的关系:
模式类图
观察者模式类图
代码示例
场景概述
模拟这样的一个场景:客户(投资者)订阅理财顾问的建议购买不同价格的股票。当价格信息变化时,所有客户会收到通知(可以使短信,邮件等等),随后客户查看最新数据并进行操作。
场景分析
一个理财顾问可能服务于多个客户,而且消息需要及时传达到各个客户那边;而客户接收到这些消息后,需要对这些消息做出相应的措施。这种一对多的通知场景我们可以使用观察者模式:理财顾问是被观察的目标(Subject),而TA的客户则是观察者(Observer)。
下面我们看一下如何用代码来模拟该场景。
代码实现
首先我们定义观察者Observer
:
//================== Observer.h ==================
@interface Observer : NSObject
{
@protected Subject *_subject;
}
- (instancetype)initWithSubject:(Subject *)subject;
- (void)update;
@end
//================== Observer.m ==================
@implementation Observer
- (instancetype)initWithSubject:(Subject *)subject{
self = [super init];
if (self) {
_subject = subject;
[_subject addObserver:self];
}
return self;
}
- (void)update{
NSLog(@"implementation by subclasses");
}
Observer
类是具体观察者的父类,它声明了一个传入目标类(Subject
)的构造方法并在构造方法里持有这个传入的实例。而且在这个构造方法里,调用了Subject
的‘添加观察者’的方法,即addObserver:
,目的是将当前的观察者实例放入Subject
的用来保存观察者实例的集合中(具体操作可以在下面讲解Subject
类的部分看到)
另外它也定义了update
方法供子类使用。
下面我们看一下具体观察者类Investor
:
//================== Investor.h ==================
@interface Investor : Observer
@end
//================== Investor.m ==================
@implementation Investor
- (void)update{
float buyingPrice = [_subject getBuyingPrice];
NSLog(@"investor %p buy stock of price:%.2lf",self,buyingPrice);
}
@end
具体观察者实现了该协议中定义的方法update
方法,在这个方法里面,首先通过getBuyingPrice
方法获得到最新的在监听的数据buyingPrice
,然后再做其他操作。这里为了方便展示,直接使用日至打印出当前的具体观察者实例的内存地址和当前监听的最新值。
下面我们声明一下目标类和具体目标类:
目标类Subject
//================== Subject.h ==================
@interface Subject : NSObject
{
@protected float _buyingPrice;
@protected NSMutableArray <Observer *>*_observers;
}
- (void)addObserver:(Observer *) observer;
- (void)removeObserver:(Observer *) observer;
- (void)notifyObservers;
- (void)setBuyingPrice:(float)price;
- (double)getBuyingPrice;
@end
//================== Subject.m ==================
@implementation Subject
- (instancetype)init{
self = [super init];
if (self) {
_observers = [NSMutableArray array];
}
return self;
}
- (void)addObserver:( Observer * ) observer{
[_observers addObject:observer];
}
- (void)removeObserver:( Observer *) observer{
[_observers removeObject:observer];
}
- (void)notifyObservers{
[_observers enumerateObjectsUsingBlock:^(Observer * _Nonnull observer, NSUInteger idx, BOOL * _Nonnull stop) {
[observer update];
}];
}
- (void)setBuyingPrice:(float)price{
_buyingPrice = price;
[self notifyObservers];
}
- (double)getBuyingPrice{
return _buyingPrice;
}
@end
目标类持有一个可变数组,用来保存观察自己的观察者们;并且还提供了增加,删除观察者的接口,也提供了通知所有观察者的接口。
而且它持有一个数据buyingPrice
,这个数据就是让外部观察者观察的数据。尤其注意它向外界提供的setBuyingPrice:
方法:当外部调用这个方法,也就是要更新buyingPrice
这个数据时,目标类调用了notifyObservers
方法来告知当前所有观察自己的观察者们:我更新了。
而getBuyingPrice
就是用来返回当前的buyingPrice
的值的,一般是在观察者们收到更新通知后,主动调动这个方法获取的(具体看上面Investor
类的实现)。
OK,现在抽象目标类定义好了,下面我们看一下具体目标类FinancialAdviser
:
//================== FinancialAdviser.h ==================
@interface FinancialAdviser : Subject
@end
//================== FinancialAdviser.m ==================
@implementation FinancialAdviser
@end
因为所有的接口的事先已经在Subject
类定义好了,所以我们只需新建一个我们需要的子类即可(如果有不同于父类的操作的话还是可以按照自己的方式定义)。
下面我们看一下观察者的机制是如何实现的:
代码语言:javascript复制FinancialAdviser *fa = [[FinancialAdviser alloc] init];
Investor *iv1 = [[Investor alloc] initWithSubject:fa];
NSLog(@"====== first advice ========");
[fa setBuyingPrice:1.3];
Investor *iv2 = [[Investor alloc] initWithSubject:fa];
Investor *iv3 = [[Investor alloc] initWithSubject:fa];
NSLog(@"====== second advice ========");
[fa setBuyingPrice:2.6];
从代码中可以看到,我们最开始向FinancialAdviser
(具体目标类)添加了一个具体观察者类的实例iv1
,然后FinancialAdviser
的实例fa
便通知了所有观察者(此时的观察者只有iv1
)。
后面我们继续向fa
添加了iv2
和iv3
后发送通知。此时三个观察者都收到了消息。
在下面的日至输出中也可以看到,内存地址0x600003094c00
就是iv1
,0x600003083680
和0x600003083690
就是iv2
和iv3
。
====== first advice ========
investor 0x600003094c00 buy stock of price:1.30
====== second advice ========
investor 0x600003094c00 buy stock of price:2.60
investor 0x600003083680 buy stock of price:2.60
investor 0x600003083690 buy stock of price:2.60
下面看一下上面代码对应的类图。
代码对应的类图
观察者模式代码示例类图
优点
- 观察者模式在观察目标和观察者之间建立了一个抽象的耦合。
- 可实现广播的,一对多的通信
缺点
- 如果一个观察目标对象有很多直接和间接的观察者的话,会需要比较多的通信时间。
- 需要注意观察者和观察目标之间是否有循环引用。
iOS SDK 和 JDK中的应用
- 在 iOS SDK 中的 KVO 与 NSNotification 是观察者模式的应用。
- 在JDK的
java.util
包中,提供了Observable
类以及Observer
接口,它们构成了Java语言对观察者模式的支持。