设计模式:基于《Head First 设计模式》的学习之路(三)--观察者模式

一、概述:什么是观察者模式

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

观察者模式包含以下角色:

主题(Subject):被观察的对象,维护一个观察者列表,提供添加、删除和通知观察者的方法。

观察者(Observer):观察主题对象的状态变化,实现 Update 方法来更新自己的状态。

具体主题(ConcreteSubject):继承自抽象主题,实现具体业务逻辑。

具体观察者(ConcreteObserver):继承自抽象观察者,实现具体业务逻辑。

使用观察者模式可以使得系统更加灵活、可扩展,减少各个模块之间的耦合度。

二、问题的提出

1.需求描述

建立一个Internet气象观测站,由WeatherData对象负责追踪目前的天气状况(温度、湿度、气压),这些信息需要显示在如下三种布告板上:1.目前的状况;2.气象统计;3.简单的预报。当天气状况发生变化时,三种布告板必须实时更新。此外,后续还可以随意添加别的布告板。

2.需求分析

整个气象观测系统的组成如下图:

 我们的工作就是:建立一个应用,利用WeatherData对象取得数据,并更新三个布告板。

三、开始工作解决问题

1.WeatherDate的内容

首先了解下WeatherData中都有什么:

类定义:

class WeatherData
{
    public:
    //我们不在乎这些气象变量如何被设置
    double getTemperature();
    double getHumidity();
    double getPressure();

    //一旦气象测量数据被更新,此方法会被调用.我们不在乎是怎么实现的
    void measurementsChanged()
    {
        //你的代码加在这里
    }
};

再明确下我们的工作:实现measurementsChanged(),好让它更新目前状况、气象统计和天气预告的显示布告板。

2.错误的示范

先看一种解决方式:

class WeatherData
{
    public:
    //我们不在乎这些气象变量如何被设置
    double getTemperature()
    {//TODO something
        return double();
    }
    double getHumidity()
    {//TODO something
        return double();
    }
    double getPressure()
    {//TODO something
        return double();
    }
    //一旦气象测量数据被更新,此方法会被调用.我们不在乎是怎么实现的
    void measurementsChanged()
    {
        //1.获取气象数据
        double temp = getTemperature();
        double humidity = getHumidity();
        double pressure = getPressure();
        //2.现在更新公告板
        currentConditionDisplay.update(temp,humidity,pressure);
        statisticsDisplay.update(temp,humidity,pressure);
        forecastDisplay.update(temp,humidity,pressure);
    }
    //其他方法
};

这种实现有什么问题呢?似乎是实现了功能啊?

回想之前的教材中,面向对象设计中一个原则:针对接口编程而不是针对实现编程。

针对具体实现编程,会导致我们以后在增加或者删除公告板时必须修改程序。

既然需求是一对多,那我们用观察者模式来试试。不过,在试之前,先了解下观察者模式,然后再回过头将其应用到气象观测站程序。

三、详述观察者模式

1.定义观察者模式

我们先看看报纸和杂志的订阅是怎么回事:

(1)报社的业务就是出版报纸。

(2)向某家报社订阅报纸,只要他们有新报纸出版,就会给你送来。只要你是他们的订户,你就会一直收到新报纸。

(3)当你不想再看报纸的时候,取消订阅,他们就不会送新报纸来了。

(4)只要报社还在运营,就会一直有客户订阅他们的报纸,或者取消订阅。

其实上述示例就是一个典型的观察者模式:出版者+订阅者=观察者模式

出版者改成为“主题”(Subject),订阅者改称为“观察者”(Observer)

所以当你试图勾勒观察者模式时,可以利用报纸订阅服务,以及出版者和订阅者比拟这一切。

你通常会看到观察者模式会被定义成这样:

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

如上图:主题和观察者定义了一对多的关系。观察者依赖于此主题,只要主题状态一有变化,观察者就会被通知。根据通知的风格,观察者可能因为此新值而更新。

2.观察者模式类图

(图片来源于《Head First 设计模式》第一版)

当两个对象之间松耦合,他们依然可以交互,但是不太清楚彼此的细节。

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

下面来详细解释:

关于观察者的一切,主题只知道观察者实现了某个接口(也就是Observer接口)。主题不需要知道观察者的具体类是谁、做了些什么或者其他任何细节。

任何时候我们都可以增加新的观察者。因为主题唯一依赖的东西是一个实现Observer接口的对象列表,所以我们可以随时增加、删除观察者而主题不会受到任何影响。

有新类型的观察者出现时,主题代码不需要修改,我们所要做的就是在新的类里实现此观察者接口,然后将其注册为观察者即可。主题不在乎别的,它只会发送通知给所有实现了观察者接口的对象。

我们可以独立的复用主题或者观察者。如果我们需要再其他地方使用主题或者观察者,可以轻易的复用,因为二者并非紧耦合。

改变主题或者观察者其中一方,并不会影响另一方。因为两者是松耦合的,所以只要他们之间的接口任被遵守,我们就可以自由地改变他们。

好了,在理解了观察者模式后,我们继续回到设计气象站应用软件的工作中来。

四、设计气象站

1.首先是类图设计

 

2.接口与类实现

根据上面的类图,我们来实现这个系统。

首先是接口:

//观察者接口
class Observer
{
    public:
    //输入参数会传递给观察者
    virtual void update(double temp,double humidity,double pressure)=0;
};

//主题接口
class Subject
{
    public:
    //这两个方法都需要一个观察者作为输入,该观察者是用来注册或者删除的
    virtual void registerObserver(Observer *o);
    virtual void removeObserver(Observer *o);

    //当主题状态改变时,这个方法会被调用,已通知所有观察者
    virtual void notifyObserver()=0;
};

//此处单独定义一个布告板显示内容的接口
class DisplayElement
{
    public:
    virtual void display()=0;
};

 接下来在weatherData类中实现Subject主题接口:

//WeatherData类
class WeatherData:public Subject
{
    private:
    std::vector<Observer> observers;
    double temperature;
    double humidity;
    double pressure;
    public:
    //注册观察者
    void registerObserver(Observer &o)
    {
        observers.push_back(o);
    }
    //取消注册
    void removeObserver(Observer &o)
    {
        auto itor =std::find(observers.begin(),observers.end(),o);
        if (itor!=observers.end())
        {
            observers.erase(itor);
        }
    }
    //通知所有观察者由新的数据。因为每个观察者都实现了updata(),所以我们知道如何通知他们
    void notifyObserver()
    {
        for (auto &o:observers)
        {
            o.update(temperature,humidity,pressure);
        }
        
    }
     //根据需求中提供的信息,一旦气象测量数据被更新,此方法会被自动调用.我们不在乎是怎么实现的
    void measurementsChanged()
    {
        notifyObserver();
    }
    //模拟气象站获取气象数据
    void setMeasurements(double temp,double humidity,double pressure)
    {
        this->temperature = temp;
        this->humidity=humidity;
        this->pressure=pressure;
        measurementsChanged();
    }
    //get方法由气象站提供
    //我们不在乎这些气象变量如何被设置
    double getTemperature();
    double getHumidity();
    double getPressure();
};

现在是布告板:实现观察者Observer接口

需求中有三个布告板:1.目前的状况;2.气象统计;3.简单的预报。在此以目前天气状况布告板为例:

//当前状况布告板类:观察者Observer接口实现类
class  CurrentConditionDisplay:public Observer,public DisplayElement
{
    private:
    double temperature;
    double humidity;
    Subject *weatherData;//有一个主题指针
    public:
    CurrentConditionDisplay(Subject *wData)
    {
        this->weatherData = wData;
        weatherData->registerObserver(this);//在主题中注册观察者
    }
    //实现Observer接口中虚函数方法:观察者对象更新到主题中的新数据
    void update(double temp,double humidity,double pressure)
    {
        this->temperature=temp;
        this->humidity=humidity;
        display();
    }
    //实现DisplauElement接口中显示的方法
    void display()
    {
        std::cout<<"Current conditions:\n";
        std::cout<<"temperature:"<<temperature<<"\n";
        std::cout<<"humidity:"<<humidity<<std::endl;
    }
};

其他两个布告板类似上述。若后续由新布告板需求,也是一样的。可以看到,新增布告板并不会影响主题。

3.测试程序

接下来让我们通过一个测试程序来启动气象站:

//测试程序
int main()
{
    //先建立一个WeatherData对象
    WeatherData *weatherData = new WeatherData();//也可以用多态 : Subject *weatherData = new WeatherData();
 
    //建立三个布告板
    //1.当前状态布告板
    //也可以用多态 : Observer *curCodDisplay = new CurrentConditionDisplay(weatherData);
    CurrentConditionDisplay *curCodDisplay = new CurrentConditionDisplay(weatherData);
    //2.气象统计布告板
    //3.简单预告布告板

    //模拟气象站获取到气象数据。每当从气象站检测设备获取到新的数据后,就会立即更新到所有的布告板中
    weatherData->setMeasurements(80,60,90);
    weatherData->setMeasurements(90,69,88);
    return 0;
}

可以看到,每个组件都很容易“插拔”。

4.主动推送与主动拉取

上面的示例中,主题(气象站)在有状态变化时,主动将数据推送给了观察者(各个布告板),观察者(各个布告板)被动的接受了这些数据。

也可以让观察者利用主题的get方法来主动拉取数据。有兴趣的小伙伴可以自己根据《Head First 设计模式》给出的java内置的方法研究下哦~

五、观察者模式结语

观察者模式的用途还是挺常见的。比如现在网络自媒体的推送,又比如某些UI界面中各种空间与对应事件的响应,Qt的信号槽机制等等。

以上为观察者模式,希望对大家有帮助。

感谢Header first 设计模式这本书,写的真棒!后面会继续整理。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三零散人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值