美文网首页
Head_First设计模式(二)----观察者模式

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

作者: 河马流星锤 | 来源:发表于2016-06-17 16:45 被阅读44次

    简述设计模式

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

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

    例子: 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]


    相关文章

      网友评论

          本文标题:Head_First设计模式(二)----观察者模式

          本文链接:https://www.haomeiwen.com/subject/mtyvdttx.html