面向对象设计的设计模式(十八):观察者模式

2019-06-11 15:49:30 浏览数 (1)

定义

观察者模式(Observer Pattern):定义对象间的一种一对多的依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象都可以到通知并做相应针对性的处理。

适用场景

凡是涉及到一对一或者一对多的对象交互场景都可以使用观察者模式。通常我们使用观察者模式实现一个对象的改变会令其他一个或多个对象发生改变的需求,比如换肤功能,监听列表滚动的偏移量等等。

现在我们清楚了观察者模式的适用场景,下面看一下观察者模式的成员和类图。

成员与类图

成员

观察者模式有四个成员:

  • 目标(Subject):目标是被观察的角色,声明添加和删除观察者以及通知观察者的接口。
  • 具体目标(Concrete Subject):具体目标实现目标类声明的接口,保存所有观察者的实例(通过集合的形式)。在被观察的状态发生变化时,给所有登记过的观察者发送通知。
  • 观察者(Observer):观察者定义具体观察者的更新接口,以便在收到通知时实现一些操作。
  • 具体观察者(Concrete Observer):具体观察者实现抽象观察者定义的更新接口。

下面通过类图来看一下各个成员之间的关系:

模式类图

观察者模式类图

代码示例

场景概述

模拟这样的一个场景:客户(投资者)订阅理财顾问的建议购买不同价格的股票。当价格信息变化时,所有客户会收到通知(可以使短信,邮件等等),随后客户查看最新数据并进行操作。

场景分析

一个理财顾问可能服务于多个客户,而且消息需要及时传达到各个客户那边;而客户接收到这些消息后,需要对这些消息做出相应的措施。这种一对多的通知场景我们可以使用观察者模式:理财顾问是被观察的目标(Subject),而TA的客户则是观察者(Observer)。

下面我们看一下如何用代码来模拟该场景。

代码实现

首先我们定义观察者Observer:

代码语言:javascript复制
//================== 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:

代码语言:javascript复制
//================== 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

代码语言:javascript复制
//================== 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

代码语言:javascript复制
//================== 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添加了iv2iv3后发送通知。此时三个观察者都收到了消息。

在下面的日至输出中也可以看到,内存地址0x600003094c00就是iv10x6000030836800x600003083690就是iv2iv3

代码语言:javascript复制
====== 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语言对观察者模式的支持。

0 人点赞