设计模式-Observer模式

参考书籍:
《HeadFirst 设计模式》
《设计模式-可复用面向对象软件的基础》

本文主要介绍对象行为型模式——Observer(观察者)模式,介绍的内容基于HeadFirst设计模式这本书,由于这本书是通过java编写,学习C++的朋友可能有所疑惑,因此本文借助GoF将其例子通过C++进行改编。

简介

观察者模式定义了对象间一对多的依赖关系,当一个对象(本文称其为目标对象)的状态发生改变时,所有依赖它的对象(本文称其为观察者对象)都会得到通知并被自动更新。

可能描述有些抽象,请看下图:
在这里插入图片描述

Observer模式描述了如何建立这种关系。这一模式中的关键对象就是目标(subject)观察者(observer)。一个目标可以有任意数目的观察者,一但该目标状态发生变化,所有的观察者都会得到通知。而作为对这个通知的相应,每个观察者都会查询目标以使其状态与目标状态同步。这种交互又称为发布-订阅(publish-subscribe),目标是通知发布者,观察者就是订阅者。

描述

以下是观察者模式的类图:
在这里插入图片描述

问题

根据类图,就可以描述出之前的定义了:

1、观察者模式如何实现一对多的关系?

通过观察者模式,目标是拥有状态的对象,并且可以控制这些状态;观察者使用这些状态,但这些状态都不属于它自己,需要依赖目标提供。这就可以产生一(目标)对多(观察者)的关系。

2、目标和观察者的依赖如何产生?

目标是拥有数据者,而观察者是使用数据者,在数据发生变化时,比起让多个对象共用一份数据来说,是更良好干净的OO设计。

松耦合的优势

观察者模式提供了一种对象设计,让目标和观察者之间松耦合。

对于目标,它只知道观察者实现了某个接口(Observer的接口),目标并不需要知道观察者是谁,它做了什么或其他细节。
因此,在任何时候目标都可以增加或减少观察者,目标唯一依赖的是一个实现Observer接口的对象列表。

在新类型的观察者出现时,目标代码不需要做任何的改变。而观察者只需要实现Observer中要求的接口,并注册为观察者即可。

此时,我们可以独立复用目标和观察者,因为两者并非紧耦合,而是松耦合。改变目标或观察者一方,都不会对另一方产生任何影响,只要它们直接约定的接口被遵守,我们可以自由改变它们。

送耦合的设计建立的有弹性的OO系统,能够应付变化,因为对象之间的互相依赖降到了最低。

实现

接下来,我就以HeadFirst中描述的气象站项目,进行描述和实现观察者模式。

气象站项目的系统主要有三个部分:

  1. 气象站(物理装置,获取实际气象信息);
  2. WeatherData对象(追踪气象站数据,也就是目标对象);
  3. 布告板(显示目前天气状态,也就是观察者,不同的布告栏显示不同的天气)。

基类部分

我们根据观察者模式的描述,进行设计目标和观察者的基类部分。

观察者基类

首先,是观察者的基类Observer,该基类只有一个要求,只需提供update()函数的声明即可,因此我把它设计为纯虚类,如下:

class Observer {
public:
    virtual void update(Subject& theChangeSubject) = 0;
};

我们再设计一个基类DisplayElement,用于输出观察者的数据变化,因为只需满足一个函数,因此设计成纯虚类。

class DisplayElement {
public:
    virtual void display() = 0;
};

目标基类

接下来是目标的基类Subject,该基类需满足提供:

  • 注册函数——registerObserver();
  • 去注册函数——removeObserver();
  • 通知函数——notifyObservers();
  • 保存订阅的观察者的数据——list容器。

根据描述,Subject类可实现为如下:

#include <list>
#include <algorithm>

class Subject {
public:
    virtual void registerObserver(Observer& o); //注册函数
    virtual void removeObserver(Observer& o);   //去注册函数
    virtual void notifyObservers();             //通知函数
    void setChange();                           //用于控制通知函数
protected:
    Subject() = default;
private:
    bool change = false;
    std::list<Observer*> observers;             //保存订阅者的容器
};

void Subject::registerObserver(Observer& o) {
    observers.emplace_back(&o);                 //将订阅者加入list,表示观察者已订阅
}

void Subject::removeObserver(Observer& o) {
    auto ret = find(observers.begin(), observers.end(), &o);    //借助标准库函数find,查找list中是否存在该观察者,若存在,则删除
    if (ret != observers.end()) {
        observers.erase(ret);
    }
}

void Subject::notifyObservers() {
    if (change) {                           //当对数据发生改变时,通知观察者
        for (auto& observer: observers) {
            observer->update(*this);        //调用所有订阅观察者的update函数,以更新观察者的数据
        }
        change = false;
    }
}

void Subject::setChange()
{
    change = true;
}

至此,观察者模式的基类部分已设计完成,接下来以气象站项目为实例,介绍派生类的实现。

派生类部分

目标类

在之前的实现章节,讲到了目标是WeatherData类,该类控制气象数据,包括温度temperature、湿度humidity、压强pressure。在之前的类图中,可以看到Subject的派生类需提供设置数据和输出数据的接口,因此添加了setMeasurements()函数用于设置数据,getTemperature()、getHumidity()、getPressure()用于输出数据。

根据上述描述,实现如下所示:

#include "Subject.h"
class WeatherData: public Subject {         //目标类继承自Subject
public:
    WeatherData() = default;
    WeatherData(float temp, float hum, float pre) :
        temperature(temp), humidity(hum), pressure(pre) {}
    void setMeasurements(float temp, float hum, float pre); //设置目标的数据

    float getTemperature();     //与下述两个函数功能相同,对外提供数据
    float getHumidity();
    float getPressure();
private:
    void measurementsChanged();             //用于通知观察者

private:
    float temperature = 0;      //设置为private,防止其他部分任意修改其参数
    float humidity = 0;
    float pressure = 0;
};

void WeatherData::measurementsChanged() {
    setChange();
    notifyObservers();
}

void WeatherData::setMeasurements(float temp, float hum, float pre)
{
    temperature = temp;
    humidity = hum;
    pressure = pre;
    measurementsChanged();
}

float WeatherData::getTemperature() {
    return temperature;
}
float WeatherData::getHumidity() {
    return humidity;
}
float WeatherData::getPressure() {
    return pressure;
}

可以看到,WeatherData类只需要关心自己控制的数据如何设置和输出,与观察者模式相关的功能均由基类Subject提供。

观察者类

观察者需在构造时描述其关心的目标类,并对目标进行注册;而且需要实现Observer类的update()函数,以保证和目标类可以进行交互,实现DisplayElement的display()输出数据,以供观察变化。

根据以上描述,观察者类设计如下:

#include "WeatherData.h"
#include <iostream>

class CurrentConditionDisplay: public Observer, public DisplayElement {
public:
    CurrentConditionDisplay(WeatherData& data): weatherData(&data) {
        data.registerObserver(*this);       //向weatherData注册
    }

    virtual void update(Subject& theChangeSubject) {    //当Subject是自己关心的对象时,根据相应的目标,调用相应的接口
        if (&theChangeSubject == weatherData) {
            this->temperature = weatherData->getTemperature();
            this->humidity = weatherData->getHumidity();
            display();
        }
    }
    virtual void display() {    //输出观察者的数据信息
        std::cout << "Current conditions: " << temperature 
                  << "F degrees and " << humidity << "% humidity" << std ::endl; 
    }
private:
    float temperature;
    float humidity;
    WeatherData *weatherData;   //保存自己关心的目标对象,这里只有WeatherData,可以观察多个对象,同样须在构造函数中向目标进行注册,在update函数中添加分支
};

至此,这个基于观察者模式的气象站项目设计完毕,可以看到,通过Subject和Observer类,目标类只需要继承Subject后,只关心自身的数据;观察者类只需要实现Obserer的update函数和向目标类进行注册。

测试

下面的测试函数,当然十分简单:

#include "WeatherData.h"
#include "CurrentConditionDisplay.h"

int main(int argc, char *argv[])
{
    WeatherData weatherData;                                //目标对象
    CurrentConditionDisplay currentDisplay(weatherData);    //观察者对象1
    CurrentConditionDisplay currentDisplay2(weatherData);   //观察者对象2
    
    weatherData.setMeasurements(80, 65, 30.4f);             //目标对象的数据改变

    weatherData.removeObserver(currentDisplay);             //去注册观察者对象1
    weatherData.setMeasurements(1, 1, 30.4f);               //目标对象数据改变
    return 0;
}

输出为:

Current conditions: 80F degrees and 65% humidity
Current conditions: 80F degrees and 65% humidity
Current conditions: 1F degrees and 1% humidity

可以看到,当有两个观察者对象注册,在目标对象weahterData的数据发生改变时,两个观察者对象都会得到通知,并更新自己的相应数据,当观察者对象1去订阅时,再当目标发生改变时,只会通知观察者对象2,而不会再通知观察者1了。

总结

自此,观察者模式已介绍完毕,事实上,观察者模式具有两个模型:推/拉模型。本文介绍的是推模型——目标改变时即推送给各个观察者;拉模型就是观察者自身去拉取目标数据,目标不再通知观察者。

两种模型都有各自的优缺点,拉模型强调目标不知道它是观察者,推模型假定目标知道一些观察者需要的信息。推模型可能使得观察者相对难以复用,因为目标对观察者的假定可能并不总是正确的;而拉模型效率可能较差,因为观察者对象再没有目标对象帮助的情况下无法确定改变了什么。

要点

  • 观察者模式定义了对象之间一对多的关系;
  • 目标通过一个共同的接口更新观察者;
  • 观察者和目标之间是松耦合的,目标不知道观察者的细节,只知道观察者提供的接口;
  • 观察者模式有推模型和拉模型(然而,推模型被认为是更“正确”的)
  • 40
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java设计模式是一组经过实践验证的面向对象设计原则和模式,可以帮助开发人员解决常见的软件设计问题。下面是常见的23种设计模式: 1. 创建型模式(Creational Patterns): - 工厂方法模式(Factory Method Pattern) - 抽象工厂模式(Abstract Factory Pattern) - 单例模式(Singleton Pattern) - 原型模式(Prototype Pattern) - 建造者模式(Builder Pattern) 2. 结构型模式(Structural Patterns): - 适配器模式(Adapter Pattern) - 桥接模式(Bridge Pattern) - 组合模式(Composite Pattern) - 装饰器模式(Decorator Pattern) - 外观模式(Facade Pattern) - 享元模式(Flyweight Pattern) - 代理模式(Proxy Pattern) 3. 行为型模式(Behavioral Patterns): - 责任链模式(Chain of Responsibility Pattern) - 命令模式(Command Pattern) - 解释器模式(Interpreter Pattern) - 迭代器模式(Iterator Pattern) - 中介者模式(Mediator Pattern) - 备忘录模式(Memento Pattern) - 观察者模式Observer Pattern) - 状态模式(State Pattern) - 策略模式(Strategy Pattern) - 模板方法模式(Template Method Pattern) - 访问者模式(Visitor Pattern) 4. 并发型模式(Concurrency Patterns): - 保护性暂停模式(Guarded Suspension Pattern) - 生产者-消费者模式(Producer-Consumer Pattern) - 读写锁模式(Read-Write Lock Pattern) - 信号量模式(Semaphore Pattern) - 线程池模式(Thread Pool Pattern) 这些设计模式可以根据问题的特点和需求来选择使用,它们提供了一些可复用的解决方案,有助于开发高质量、可维护且易于扩展的软件系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值