您的位置:首页 > 其它

观察者模式(Observer Pattern)

2015-01-23 08:54 513 查看

定义

观察者设计模式也叫做发布-订阅(Publish-Subscribe)模式。有点像杂志订阅的意思,你向杂志社订阅杂志,然后提供了自己的姓名和邮箱地址,这样杂志社就可以把你所订阅的杂志推送到你的邮箱了,而你收到的杂志都是你自己订阅的,不会不是你订阅的,这就是一个观察者模式的例子。订阅杂志的过程简单来说就是一个观察者(订阅者)向某个杂志社(发布者)订阅特定的杂志,其静态关系图如下所示:



从抽象的角度来看,上图所示的观察者模式是Obsevers向Subject订阅特定的消息(即杂志),因此一旦Subject对象需要通知观察者某些变化的时候,Subject对象将会发送update消息给每个观察者,而这些观察者值得是所有存储在Subject对象的内部列表中的订阅者。观察者模式是一种简单直接的设计模式,Subject为那些实现了Observer协议并且需要使用update消息的对象提供了注册和反注册的功能。当Subject对象发生了一些变化后,它会给自己发送消息通知,然后通过特定的广播算法向所有注册了的Observer发送update消息,其时序图如下所示:



使用观察者设计模式的最明显的好处就是能够为Subject对象扩展N个观察者,一个Subject可以对应无限个观察者(无限也要考虑系统资源的),只需要注册即可。

补个观察者的官方定义吧:



适用场景

在程序设计中,将一个系统分割成一个一些类相互协作的类有一个不好的副作用,那就是需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便,观察者就是解决这类的耦合关系的。当出现以下情况的时候,你可以考虑考虑观察者模式了:

当你有两个相互独立的抽象类的时候,通过把他们封装到一个独立的类之后,你依旧可以独立地变换以及重用他们。
当一个对象的变换需要引起其他变换的时候,尤其是需要一系列的对象需要跟着变换
当一个对象需要在不知道其他对象的具体情况而需要给他们发送通知的时候

CocoaTouch中的观察者模式

IOS开发过程中使用最多的MVC模式就包含了观察者设计模式,其中V(视图控制器)就是观察者,通过观察者设计模式,由于每个组件没有和其他组件存在多少的联系,因此他们可以自由地重用和扩展。
除了MVC设计模式,在Cocoa Touch的框架中,我们也能发现观察者设计模式的应用:Notifications和KVO(Key-Value Observer)
其中Notifications一般使用方法如下:
// 添加观察者
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeIntlCode:) name:NOTIFICATION_SUCCESS_SELECT_INTLCODE object:nil];

// 发出通知
[[NSNotificationCenter defaultCenter] postNotificationName:NOTIFICATION_SUCCESS_SELECT_INTLCODE object:nil userInfo:[NSDictionary dictionaryWithObject:model forKey:@"selectedIntlCodeModel"]];

// 移除观察者
[[NSNotificationCenter defaultCenter] removeObserver:self name:NOTIFICATION_SUCCESS_SELECT_INTLCODE object:nil];
KVO其实知识点比较多,这里也拿一些比较常见的代码来看看使用情况:
{
book = [[Book alloc] init];

// 通过setValue:forKey设置值
[book setValue:@"18.8" forKey:@"price"];
// 监视注册属性
[book  addObserver:self forKeyPath:@"price" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:NULL];

// ....

}

// NSKeyValueObserving定义的借口方法,重写实现回调方法
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if([keyPath isEqualToString:@"price"])
{
// 通过valueForKy获取值
NSLog(@"%@", [book valueForKey:@"price"]);
}
}

- (void)dealloc
{
[super dealloc];
[book removeObserver:self forKeyPath:@"price"];
[book release];
}


代码示例

使用读者向杂志社订阅电子杂志做例子,需要抽象出观察者和被观察者,其中Subject是个协议,Publisher是杂志社,他们的定义实现如下:
//
//  Publisher.h
//  ObserverDemo
//
//  Created by God Lin on 15/1/22.
//  Copyright (c) 2015年 arbboter. All rights reserved.
//

#import <Foundation/Foundation.h>

@class Observer;

// 定义抽象主题协议
@protocol Subject <NSObject>
@required
// 注册观察者
- (void) doRegister:(Observer*) observer;
// 反注册观察者
- (void) unRegister:(Observer*) observer;
// 为观察者发送通知
- (void) notifyObservers;
@end

// 出版社
@interface Publisher : NSObject <Subject>
@property (nonatomic, strong) NSMutableArray* observerArray;

// 新出版了杂志
- (void) pulishNew;
@end
//
//  Publisher.m
//  ObserverDemo
//
//  Created by God Lin on 15/1/22.
//  Copyright (c) 2015年 arbboter. All rights reserved.
//

#import "Publisher.h"
#import "Observer.h"

@implementation Publisher

- (id) init
{
if(self = [super init])
{
_observerArray = [[NSMutableArray alloc] init];
}
return self;
}

#pragma mark -- 实现协议Subject
// 注册观察者
- (void) doRegister:(Observer*) observer
{
[self.observerArray addObject:observer];
}
// 反注册观察者
- (void) unRegister:(Observer*) observer
{
if([self.observerArray indexOfObject:observer] != NSNotFound)
{
[self.observerArray removeObject:observer];
}
}
// 为观察者发送通知
- (void) notifyObservers
{
NSLog(@"我杂志社新发布了杂志了!!!");
// 通知每个注册了得观察者
for (Observer* o in self.observerArray)
{
[o foundNew];
}
}

- (void) pulishNew
{
// 发布通知
[self notifyObservers];
}

@end
抽象出来的观察者Observer实现定义如下,该抽象是Subject所知道的,Subject熟知该抽象接口:
#import <Foundation/Foundation.h>

@interface Observer : NSObject
// 通知观察者有新发现
- (void) foundNew;
@end
#import "Observer.h"

@implementation Observer
// 通知观察者有新发现
- (void) foundNew
{
NSLog(@"I Know.");
}
@end
读者是继承抽象的Observer,实现重写接口的,具体实现如下:
#import "Observer.h"

@interface Reader : Observer

@property (nonatomic, strong) NSString* name;

- (id) initWithName:(NSString*)myName;
@end


#import "Reader.h"

@implementation Reader
- (id) initWithName:(NSString*)myName
{
if(self = [super init])
{
self.name = myName;
}
return self;
}
- (void) foundNew
{
NSLog(@"[%@]哇哈哈,又有好东西可以看了!!!", self.name);
}
@end


测试代码如下:
//
//  main.m
//  ObserverDemo
//
//  Created by God Lin on 15/1/22.
//  Copyright (c) 2015年 arbboter. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "Publisher.h"
#import "Reader.h"
int main(int argc, const char * argv[])
{
Publisher* publisher = [[Publisher alloc] init];

NSString* name = nil;
Reader* reader = nil;
// 保存为了反注册
NSMutableArray* readers = [[NSMutableArray alloc] init];
for (NSInteger i=0; i<5; i++)
{
name = [NSString stringWithFormat:@"reader %ld", i+1];
reader = [[Reader alloc] initWithName:name];

// 注册
[publisher doRegister:reader];

// 为了反注册
[readers addObject:reader];
}

// 杂志社出新杂志了
[publisher pulishNew];

// 反注册
for (Reader* r in readers)
{
[publisher unRegister:r];
}

return 0;
}
输出结果为:
2015-01-22 23:47:38.637 ObserverDemo[19997:38042854] 我杂志社新发布了杂志了!!!
2015-01-22 23:47:38.638 ObserverDemo[19997:38042854] [reader 1]哇哈哈,又有好东西可以看了!!!
2015-01-22 23:47:38.639 ObserverDemo[19997:38042854] [reader 2]哇哈哈,又有好东西可以看了!!!
2015-01-22 23:47:38.639 ObserverDemo[19997:38042854] [reader 3]哇哈哈,又有好东西可以看了!!!
2015-01-22 23:47:38.639 ObserverDemo[19997:38042854] [reader 4]哇哈哈,又有好东西可以看了!!!
2015-01-22 23:47:38.639 ObserverDemo[19997:38042854] [reader 5]哇哈哈,又有好东西可以看了!!!
Program ended with exit code: 0
可以从上述代码中看到,Subject需要依赖于观察者的抽象接口(一个缺点),需要在通知的时候使用者写接口。读者通过向杂志社发送注册消息后,当杂志社新出版了杂志后,每个杂志的订阅者可以立刻获取最新的杂志。

总结

观察者模式可以使得订阅者及时获取被观察者的变换情况,支持多个观察者,同时也降低了Subject和具体的Observer的耦合性。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息