Head_First设计模式(二)----观察者模式

简述设计模式

观察者模式: 定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

使用条件: 当你需要将改变通知所有的对象时,而你又不知道这些对象的具体类型,此时就可以使用观察者模式。 改变发生在同一个对象中,并在别的地方需要将相关的状态进行更新。

例子: Head_First中是以气象局天气预报为例子接下来会给出代码, 书中是以JAVA为语言基础给出的例子, 这里作者主要做的是iOS开发, 所以翻译成OC进行举例。

相关知识点和思路

涉及到的知识点

主要包括****: NSNotification, ****封装和协议的使用****(****对应部分会在代码中详细标注****)****。

思路分析

在开发过程中会经常遇到一些实际情况, 这里直接拿书中的气象台来举例了。

设计原理

图形解析: 首先是数据采集部分, 和我们程序员关系不大。我们主要负责的是从WeatherData取数据到各个发布板展示的过程。即上图右半部分, 这里请不要纠结数据咋来的, 我们只当他采集到了并且付给了我们的WeatherData对象。

添加观察者

我们想要得到实时变化的数据, 首先需要添加观察者, 即我们想知道天气变化,我们需要打开电视看气象台卫视一样。

订阅

****这里的主题对象即上文的****WeatherData, ****狗**** ****猫**** ****老鼠**** ****这些对象即对应的各个显示装置****

移除观察者

当我们不在关心天气变化时, 我们换了台, 即不在关注WeatherData, 这里我们就要移除观察者。 (********务必在不用时移除****, ****否则会很蛋疼********)

取消订阅

很显然这里如果我们使用观察者很容易解决温度, 气压等实时更新的问题, 而且也符合我们的订阅和取消订阅原则。

代码实现

  • 观察者模式在iOS种大体可分为三类

  • 1.****Notification(****这是苹果给的类拿过来直接用就行****)****

  • 2.****KVO****

  • 3.****标准方法****

1.Notification

官方API

/* 获取系统的默认通知中心 */

+(NSNotificationCenter *)defaultCenter; 

/* 添加观察者 */
/**
 * @observer: 主题对象(上文的WeatherData)
 * @(SEL)aSelector: 主题对象改变时通知观察者执行的方法
 * @aName: 确认身份的名字(通过这个连接主题和观察者)
 * @anObject: 想了主题解数据改变并得到回复的观察者(上文的狗,猫等显示装置)
 */
 
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSString *)aName object:(nullable id)anObject;

/**
 * @name: 确认身份的名字(通过这个连接主题和观察者)
 * @obj: 想了解主题数据改变并得到回复的观察者(上文的狗,猫等显示装置),如果nil 所有观察者都会收到
 * @queue: 有反馈结果时需返回到的线程一般为[NSOperationQueue mainQueue]
 * @block: 这里可以获得改变的数据和做对应处理即上个方法中的@(SEL)aSelector所执行的操作
 */ 

- (id <NSObject>)addObserverForName:(nullable NSString *)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block NS_AVAILABLE(10_6, 4_0);

/* 发送数据 */

- (void)postNotification:(NSNotification *)notification;

- (void)postNotificationName:(NSString *)aName object:(nullable id)anObject;

- (void)postNotificationName:(NSString *)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;/* @aUserInfo: 是用于装主题数据改变的字典, 就是把这个传给观察者 */

实际使用

/* 添加观察者(订阅) */
- (void)viewDidLoad
{
    [super viewDidLoad];
    
 NSNotificationCenter  *notificationCenter = [NSNotificationCenter  defaultCenter];    /* 获取系统的通知中心单利 */

 [notificationCenter addObserverForName:@"valueChange" object:@"123" queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
        /**
         * @note: 里面有传过来的数据 直接使用note.userInfo调用。
         */
    }]; 
}

/* 主题数据改变处理发送部分 */
- (IBAction)btnNotificationCenterTest:(id)sender { /* 这是xib直接托的控件 */
 /* 通过点击button发送通知 */
 [notificationCenter postNotificationName:@"valueChange" object:@"123" userInfo:@{@"key":@"change"}];
 
}

/* 从通知中心(NSNotificationCenter)中移除观察者 */
- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];
    
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"valueChange" object:@"123"];
    /* 这里需要注意的一般都是在界面消失的时候移除观察者, 当然也有例外, 如果是从别的界面向这个界面传值时, 需要这个界面事先是存在的(即已经创建), 这时就不能在界面消失的方法里移除, 需要处理的是无论界面消失或存在都只添加一次观察者, 不要重复添加 */
}

PS: 观察者需要先添加, 不然发送了也收不到。就想订阅报纸, 你还没定呢, 送报纸的肯定不会给你送。所以需要先添加之后再发送数据。

这种方法我比较喜欢, 配合全局变量可以随便传值无国界跨时代, 而且快。比之属性传值, 代理传值好的不是一星半点。

KVO

解释: KVO也是苹果提供的方法, 为对象添加监测对象属性变化的观察者。比较常见的就是很多APP种通过滑动tableview控制NavigationBar变透明或者显示的操作。

官方API

/**
 * @observer: 添加观测的对象(上文中的WeatherData)
 * @keyPath: 对象的可变属性(温度, 压强)
 * @options: 这是一个枚举 一般会填 1 | 2 属性变换的新旧值
 * @context: 上下文, 一般用不到填nil
 */
- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;

实际使用

单独写一个KVO观测者类继承OBJECT, 名字随便我这里叫KVOObserver。这个单独封装出来好管理
在建一个要观察的对象类 , 名字叫KVOObject。

观察者KVOObserver

/* KVOObserver的.h */
/* 封装 */
#import <Foundation/Foundation.h>

@interface KVOObserver : NSObject

@end

#import "KVOObserver.h"

/* KVOObserver的.m */
@implementation KVOObserver

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    
    NSLog(@"%@", change);
    
    NSLog(@"KVO:值发生了改变");
}

@end

主题对象(WeatherData)KVOObject
/* KVOObject.h */
/* 只是为举例子创建的类, 工程中使用时不要这个, 直接将观察者添加给你在使用的类, 观察其属性即可 */
#import <Foundation/Foundation.h>

@interface KVOSubject : NSObject

@property (nonatomic, strong) NSString changeableProperty;/* 供观察的属性 */

@end

/* KVOObject.m */
#import "KVOSubject.h"

@implementation KVOSubject

@end

使用
/* 引入上面类的头文件 */

#import "KVOSubject.h"
#import "KVOObserver.h"

#pragma mark- KVO

- (IBAction)btnKVOObservationTest:(id)sender { /* 点击事件 */
    
    KVOSubject *kvoSubj = [[KVOSubject alloc] init];
    
    KVOObserver *kvoObserver = [[KVOObserver alloc] init];
    
    [kvoSubj addObserver:kvoObserver forKeyPath:@"changeableProperty"
                 options:1 | 2 context:nil]; /* @options: 1|2 代表 NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld */
    
    kvoSubj.changeableProperty = @"我是GS";
    
 kvoSubj.changeableProperty = @"我是GSSS111111"; /* 改值 */
    
    //[kvoSubj removeObserver:kvoObserver forKeyPath:@"changeableProperty"]; /* 也需要移除, 也需要移除, 也需要移除 */
}

  • PS: 别忘记移除
结果

运行结果
如果上文options 中只有1 则打印结果时不会显示old

KVO结果

观测者标准模式

这种方式需要自己定义协议, 和实现方法实现观测。

Subject和Observer(观察者)的协议(protocol)创建

这里添加的只是协议方法所以只有.h
实现的部分会写在下一块

StandardObserver观察者协议
/* StandardObserver.h观察者协议 */
#import <Foundation/Foundation.h>

@protocol StandardObserver <NSObject>

- (void)valueChanged:(NSString **)valueName newValue:(NSString *) newValue; /* 主题值改变的方法 */

@end
StandardSubject主题对象协议
/* StandardSubject.h 主题对象协议 */

#import <Foundation/Foundation.h>
#import "StandardObserver.h"

@protocol StandardSubject <NSObject>

- (void)addObserver:(id<StandardObserver>)observer; /* 添加观察者 */
- (void)removeObserver:(id<StandardObserver>)observer; /* 移除观察者 */
- (void)notifyObjects; /* 通知对应的观察者对象 */

@end

主题对象创建

主题对象的.h

#import <Foundation/Foundation.h>
#import "StandardSubject.h"

@interface StandardSubjectImplementation : NSObject <StandardSubject>
{/* 签主题对象协议 */
    @private NSString *_valueName;
    @private NSString *_newValue;
}

@property (nonatomic, strong) NSMutableSet **observerCollection;/** 主题对象的观察者集合 */

-(void)changeValue:(NSString *)valueName andValue:(NSString *) newValue;

@end

主题对象的.m
#import "StandardSubjectImplementation.h"

@implementation StandardSubjectImplementation

/* 主题的观察者对象初始化, 这里使用了懒加载 */
- (NSMutableSet *)observerCollection
{
    if (_observerCollection == nil)
        _observerCollection = [[NSMutableSet alloc] init];
    
    return _observerCollection;
}

#pragma mark - 
#pragma mark 协议方法实现
/* 添加观察者 */
-(void) addObserver:(id<StandardObserver>)observer
{
    [self.observerCollection addObject:observer];
}

/* 移除观察者 */
-(void) removeObserver:(id<StandardObserver>)observer
{
    [self.observerCollection removeObject:observer];
}

/* 通知观察者 */
-(void)notifyObjects
{
    for (id<StandardObserver> observer in self.observerCollection) {
        [observer valueChanged: _valueName newValue:_newValue];
    }
}

-(void)changeValue:(NSString *)valueName andValue:(NSString *) newValue
{ 
    _newValue = newValue;
    _valueName = valueName;
    [self notifyObjects];
}

@end

观察者对象

观察者对象.h
#import <Foundation/Foundation.h>
#import "StandardObserver.h"

@interface SomeSubscriber : NSObject <StandardObserver>/* 签观察者协议 */

@end

观察者对象.m
#import "SomeSubscriber.h"

@implementation SomeSubscriber

/* 协议方法实现 */
-(void)valueChanged:(NSString *)valueName newValue:(NSString *)newValue
{
    NSLog(@"SomeSubscriber输出: 值 %@ 已变为 %@", valueName, newValue);
}

@end

使用

别忘记引入头文件

#pragma mark- 标准Observer
- (IBAction)btnStandardObservationTest:(id)sender {
    
    StandardSubjectImplementation *subj = [[StandardSubjectImplementation alloc] init];
    SomeSubscriber *someSubscriber = [[SomeSubscriber alloc] init];
    
    [subj addObserver:someSubscriber];
    
    [subj changeValue:@"version" andValue:@"1.0.0"];
}

打印结果

写的时候是添加了两个观察者对象所以显示两个结果

这里写图片描述

使用之后别忘记移除


 [subj removeObserver:someSubscriber];

PS: 以上都是比较简单的应用, 实际使用时比这里要复杂一些, 但是也很容易理解。 对于观察者模式, 只要理清了思路, 是十分好用的。而且前两种都是苹果为我们内置的观察者, 使用起来更方便。

[toc]


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容

  • 什么是观察者模式?我们先打个比方,这就像你订报纸。比如你想知道美国最近放生了些新闻,你可能会订阅一份美国周刊,然后...
    泥孩儿0107阅读 672评论 0 0
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,644评论 18 139
  • 1 场景问题# 1.1 订阅报纸的过程## 来考虑实际生活中订阅报纸的过程,这里简单总结了一下,订阅报纸的基本流程...
    七寸知架构阅读 4,612评论 5 57
  • 1.什么是观察者模式?2.为什么要用观察者模式?它的优缺点是什么?![Uploading 屏幕快照 2016-12...
    羊妞麻麻阅读 729评论 0 0
  • 定义 定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,他的所有依赖者都会收到通知并自动更新. 模式特点...
    Link913阅读 236评论 0 0