1.概述

使用设计模式可以提高代码的可复用性、可扩充性和可维护性。观察者模式(Observer Pattern)属于行为型模式,在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新。

例如,在实际生活中,报纸出版社和订阅者之间存在着一对多的关系。当你向出版社订阅报纸时,只要他们有新报纸出版,就会送一份过来。当你不需要看报纸,可以取消订阅。只要报社还在运营,就会一直有人(或单位)向他们订阅报纸或取消报纸。实际上,出版社+订阅者=观察者模式。观察者模式中,出版社被称为”主题”(Subject),订阅者被称为”观察者”(Observer)。关系如下图所示:

主题与观察者定义了一对多的关系。观察者依赖于此主题,只要主题状态一有变化,观察者就会被通知。

观察者模式结构图:

Subject:抽象主题(抽象被观察者),抽象主题对象把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。

ConcreteSubject:具体主题(具体被观察者),将有关状态存入具体观察者对象,在具体主题内部状态发生改变时,给所有注册的观察者发送通知。

Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。

ConcrereObserver:具体观察者,是实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。

2.实例

下面给出实际的应用场景,以C++为例,使用观察者模式来解决问题。

假设现在需要完成公司的一个项目,气象站项目。气象站的数据由WeatherData对象提供,包括温度、湿度和气压。项目要求有两种布告板,分别显示目前状况(CurrentStatus)和气象统计(Statistics)。当WeatherData对象获得最新的测量数据时,两种布告板必须实时更新。而且,这是一个可以扩展的气象站,需要公布一组API,好让其他开发人员可以写出自己的布告板。

2.1丑陋的设计

//C++示例代码,运行于VS2015

#include <stdlib.h>
#include <iostream>
using namespace std;

//目前状况类:CurrentStatus
class CurrentStatus {
    float temperature=0;  //C++11特性,可直接给类成员数据赋默认值
    float humidity=0;
    float pressure=0;
public:
    //显示当前气象信息
    void display() {
        cout << "CurrentStatus :"<< endl;
        cout << "temperature:" << temperature << " humidity:" << humidity<<" pressure:" <<pressure << endl;
    }

    //更新状态
    void update(float temperature,float humidity,float pressure) {
        this->temperature= temperature;
        this->humidity= humidity;
        this->pressure= pressure;
        display();//显示当前气象信息
    };
};

//气象统计类:Statistics
class Statistics {
    float minTemperature=3.4e+38;//最低温度
    float maxTemperature=-3.4e+38; //最高温度
public:
    //显示气象统计
    void display() {
        cout << "Statistics :" << endl;
        cout << "minTemperature:" << minTemperature << " " << "maxTemperature:" << maxTemperature << endl;
    }

    //更新状态
    void update(float temperature,float humidity,float pressure) {
        this->minTemperature = temperature<this->minTemperature? temperature: this->minTemperature;
        this->maxTemperature= temperature>this->maxTemperature ? temperature : this->maxTemperature;
        display();//显示气象统计
    };
};

//WeatherData类
class WeatherData {
public:
    float getTemperature(){ return 1; };//仅作示意,简单实现
    float getHumidity(){ return 2; };
    float getPressure(){ return 3; };
    void measurementChanged();
};

void WeatherData::measurementChanged(){
    float temp = getTemperature();
    float humidity = getHumidity();
    float pressure = getPressure();

    CurrentStatus cs;
    cs.update(temp, humidity, pressure);
    Statistics sta;
    sta.update(temp,humidity, pressure);
}

int main() {
    WeatherData wd;
    wd.measurementChanged();
    system("pause");
}

程序执行结果:

CurrentStatus :
temperature:1 humidity:2 pressure:3
Statistics :
minTemperature:1 maxTemperature:1

上面的实现我们能够初步完成气象站所需的功能 ,但是存在以下缺点:在实现函数measurementChanged时,当气象站数据更新时,可以及时的使每一个布告板进行实时更新。但是针对具体实现编程,会导致以后再增加或删除布告板时必须修改measurementChanged。

2.2使用观察者模式

具体实现如下。

抽象观察者(Observer)。里面定义了一个更新的方法:

class Observer {
public:
    virtual void update(float temperature,float humidity,float pressure)=0;
};

具体观察者(Concrete Observer)。布告板是观察者,里面实现了更新的方法:

//目前状况布告板:CurrentStatus
class CurrentStatus:public Observer{
    ...
public:
    //具体实现更新接口函数
    void update(float temperature,float humidity,float pressure){
         //同上
    }

    //显示当前气象信息
    void display(){...} //同上
};

//气象信息统计布告板:Statistics
class Statistics:public Observer{
    ...
public:
    //具体实现更新接口函数
    void update(float temperature,float humidity,float pressure{
         //同上
    }

    //显示气象统计信息
    void display(){...} //同上

抽象被观察者(Subject)。抽象主题提供了注册attach、移除detach和通知notify三个纯虚函数,供具体被观察者实现:

class Subject{
public:
   virtual void attach(Observer*)=0;
   virtual void detach(Observer*)=0;
   virtual void notify()=0;
};

具体被观察者(Concrete Subject)。这里的WeatherData类是具体主题(具体被观察者),具体实现如下:

class WeatherData:public Subject{
    float temperature=0;
    float humidity=0;
    float pressure=0;
    list<Observer*> list; //用于记录注册的观察者
public:
    //实现注册
    void attach(Observer* o){
        list.push_back(o);
    }
    //实现移除
    void detach(Observer* o){
        for(auto it=list.begin();it!=list.end();++it){
            if(*it==o){
                list.erase(it);
                break;
            }
        }
    }
    //实现通知所有的观察者
    void notify(){
         for(auto it=list.begin();it!=list.end();++it){
                (*it)->update(temperature,humidity,pressure);
            }
        }

    //设置观测值
    void setMeasurements(float temperature,float humidity,float pressure){
        this->temperature= temperature;
        this->humidity= humidity;
        this->pressure= pressure;
        this->notify();//观测值更新,通知观察者
    }

    //WeatherData的其他方法
};

气象站已经通过观察者模式完成了建立,下面开始测试。

int main() {
    WeatherData wd;
    //注册布告板(观察者)
    wd.attach(new CurrentStatus);
    wd.attach(new Statistics);

    //检测到新的气象值,通知布告板进行更新
    wd.setMeasurements(1.0,2.0,3.0);
    wd.setMeasurements(1.1, 2.1, 3.1);
    system("pause");
}

程序运行结果:

CurrentStatus :
temperature:1 humidity:2 pressure:3
Statistics :
minTemperature:1 maxTemperature:1
CurrentStatus :
temperature:1.1 humidity:2.1 pressure:3.1
Statistics :
minTemperature:1 maxTemperature:1.1

我们成功了使用了观察者模式,完成了气象站项目的设计和实现。观察者模式提供了一种对象设计,让主题和观察者之间松耦合,它们之间依然可以交互,但是不太清楚彼此的细节。

任何时候我们都可以增加新的观察者,因为主题唯一依赖的东西是一个实现Observer接口的对象列表。事实上,在运行时我们可以用新的观察者取代现有的观察者,主题不会受到任何影响。同样的,也可以在任何时候删除某些观察者,或者注册某些观察者。此外,当有新类型的观察者出现时,主题的代码不需要修改,只要将新类型的观察者实现Observer接口,然后注册即可。

事实上,我们还可以独立的复用主题或观察者,并且改变主题或观察者中的一方,不会影响另一方,因为二者是松耦合。松耦合的设计可以让我们建立有弹性的OO系统,能够应对变化,因为对象之间的相互依赖降到了最低。所以,我们要坚持一个OO设计原则:为了交互对象之间的松耦合设计而努力

3.观察者模式的应用场景和优缺点

使用场景:

(1)关联行为场景。需要注意的是,关联行为是可拆分的,而不是“组合”关系。

(2)事件多级触发场景。跨系统的消息交换场景,如消息队列、事件总线的处理机制。

优点:

解除耦合,让耦合的双方都依赖于抽象,从而使得各自的变换都不会影响另一边的变换。

缺点:

在应用观察者模式时需要考虑一下开发效率和运行效率的问题。程序中包括一个被观察者、多个观察者,开发、调试等内容会比较复杂,而且消息的通知一般是顺序执行,如果一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般会采用异步实现。

4.小结

(1)OO设计原则:为了交互对象之间的松耦合设计而努力。

(2)观察者模式:在对象之间定义一对多的依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。


参考文献

[1]Freeman E.,Freeman E.,Sierra K.,et al.设计模式[M].第一版O’Reilly Taiwan公司译.北京:中国电力出版社,2015:38-75

[2]设计模式(五)观察者模式

设计模式 (二)——观察者模式(Observer,行为型)的更多相关文章

  1. 乐在其中设计模式(C#) - 观察者模式(Observer Pattern)

    原文:乐在其中设计模式(C#) - 观察者模式(Observer Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 观察者模式(Observer Pattern) 作者:weba ...

  2. 8.5 GOF设计模式四: 观察者模式Observer

    GOF设计模式四: 观察者模式Observer  现实中遇到的问题  当有许多不同的客户都对同一数据源感兴趣,对相同的数据有不同的处理方式,该如 何解决?5.1 定义: 观察者模式  观察者模式 ...

  3. 二十四种设计模式:观察者模式(Observer Pattern)

    观察者模式(Observer Pattern) 介绍定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新. 示例有一个Message实体类,某些对象 ...

  4. 设计模式 笔记 观察者模式 Observer

    //---------------------------15/04/27---------------------------- //Observer 观察者模式----对象行为型模式 /* 1:意 ...

  5. 人人都会设计模式:观察者模式--Observer

    https://segmentfault.com/a/1190000012295887 观察者模式是抽像通知者和观察者,达到具体通知者跟具体观察者没有偶合.能达到不管是切换通知者,或者是切换观察者,都 ...

  6. 行为型设计模式之观察者模式(Observer)

    结构 意图 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新. 适用性 当一个抽象模型有两个方面, 其中一个方面依赖于另一方面.将这二者封装在独 ...

  7. 设计模式九: 观察者模式(Observer Pattern)

    简介 观察者属于行为型模式的一种, 又叫发布-订阅模式. 如果一个对象的状态发生改变,依赖他的对象都将发生变化, 那么这种情况就适合使用观察者模式. 它包含两个术语,主题(Subject),观察者(O ...

  8. [设计模式] 19 观察者模式 Observer Pattern

    在GOF的<设计模式:可复用面向对象软件的基础>一书中对观察者模式是这样说的:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新.当一个 ...

  9. 设计模式之观察者模式(Observer)(4)

    简介 观察者模式(Observer)完美的将观察者和被观察的对象分离开.举个例子,用户界面可以作为一个观察者,业务数据是被观察者,用户界面观察业务数据的变化,发现数据变化后,就显示在界面上.面向对象设 ...

随机推荐

  1. VitualBox安装linux记录

    下载镜像 CentOS 7镜像下载 阿里云站点:http://mirrors.aliyun.com/centos/7/isos/x86_64/ VirtualBox安装linux https://ww ...

  2. MPI-Hydra Process Managerment Framework

    1. 概述2. 执行过程和控制流 官方文档地址:https://wiki.mpich.org/mpich/index.php/Hydra_Process_Management_Framework 1. ...

  3. for...else 小记

    for ......: ...... else: ...... 在 for 循环中,若没有执行 break ,正常结束,则会执行 else 中的语句. 若执行了 break , 则 不会 执行 els ...

  4. libmysqlclient.so.16: cannot open shared object file: No such file or directory

    编译安装的mysql5.6.39,安装目录是/usr/local/mysql,启用程序时报错:libmysqlclient.so.16: cannot open shared object file: ...

  5. L2-027. 名人堂与代金券

    链接:名人堂与代金券 在比赛中这题只得了2分,赛后发现原来strcmp函数并不是只返回-1,0,1三种,而是返回正数负数0 但是在我的电脑上一般就是返回前三种,只是用后面的三种更稳妥点 都怪我基础不扎 ...

  6. LINUX内核分析第八周总结:进程的切换和系统的一般执行过程

    一.进程调度与进程切换 1.不同的进程有不同的调度需求 第一种分类: I/O密集型(I/O-bound) 频繁的进行I/O 通常会花费很多时间等待I/O操作的完成 CPU密集型(CPU-bound) ...

  7. Leetcode——171.Excel表列序号【水题】

    @author: ZZQ @software: PyCharm @file: leetcode171_Excel表列序号.py @time: 2018/11/22 15:29 要求: 给定一个Exce ...

  8. ThiNet: A Filter Level Pruning Method for Deep Neural Network Compression笔记

    前言 致力于滤波器的剪枝,论文的方法不改变原始网络的结构.论文的方法是基于下一层的统计信息来进行剪枝,这是区别已有方法的. VGG-16上可以减少3.31FLOPs和16.63倍的压缩,top-5的准 ...

  9. 每天学习SQL

    SELECT table_name FROM information_schema.tables WHERE table_schema='survey170227_main' AND table_na ...

  10. PAT L2-022 重排链表

    https://pintia.cn/problem-sets/994805046380707840/problems/994805057860517888 给定一个单链表 L​1​​→L​2​​→⋯→ ...