• 设计模式入门(二)观察者模式


    设计模式入门

    本系列所有内容参考自《HeadFirst设计模式》。因为书中的代码是采用java语言写的,博主这里用C++语言改写。
    这里采用讲故事的方式进行讲解。若有错误之处,非常欢迎大家指导。
    设计模式:模式不是代码,而针对设计问题的通用解决方案,被认为是历经验证的OO设计经验。设计模式告诉我们如何组织类和对象以解决某种问题。
    如果你输出一个helloworld都想使用设计模式的话,那可能真的就有问题了。

    正文

    提出问题

    我们现在手头有一个气象检测应用。气象站接收湿度感应装置温度感应装置气压感应装置的数据,然后我们有一个WeatherData对象,它负责追踪来自气象站的数据,并更新布告板(显示目前天气状况给用户看)。
    图片来自HeadFirst设计模式
    如果我们要接手这个项目,我们的工作就是建立一个应用,利用WeatherData对象取得数据,并更新三个布告板:目前状况、气象统计和天气预报。三个布告板如下图所示:
    在这里插入图片描述

    现有的WeatherData类源码如下:

    class WeatherData {
        float getTemperature();  //返回温度
        float getHumidity();     //返回湿度
        float getPressure();     //返回气压
        void measurementsChanged()
        {
            /*一旦气象测量更新,此方法会被调用*/
            //我们的代码加在这里
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

    我们目前知道的:WeatherData类有三个方法,可以取得三个测量值;当新的数据来临时,measurementsChanged()方法就会被调用(我们不在乎此方法是如何被调用的,我们只在乎它被调用了);我们需要实现三个使用天气数据的布告板,一旦WeatherData有新的测量,这些布告必须马上更新。

    一个我们可能想到的measurementsChanged()实现如下:

    class WeatherData {
    	// 实例变量声明
        void measurementsChanged()
        {
        	// 获取最新的测量值
            float temp = getTemperature();
            float humidity = getHumidity();
            float pressure = getPressure();
            
            // 调用每个布告板更新显示
            currtenConditionsDisplay.update(temp, humidity, pressure);  // 目前状况布告板更新
            statisticsDisplay.update(temp, humidity, pressure);  // 气象统计布告板更新
            forecastDisplay.update(temp, humidity, pressure);   // 天气预报布告板更新
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    但是这与一些软件设计原则发生了矛盾。上面代码中调用每个布告板更新显示函数是针对具体实现编程,会导致我们以后在增加或删除布告板时必须修改程序;三个接口都是update,传入的参数也是一样的,所以看起来更像是一个统一的接口。

    那我们该如何解决这个问题呢?观察者模式可以帮助我们很好地解决这个问题。

    观察者模式

    一个很简单的例子就是杂志订阅
    假设我们订阅了一款杂志,每当这款杂志更新时,它都会给我们送一份。这就是观察者模式,杂志相当于“主题”,我们相当于“观察者”,当主题发生改变时,就是通知“观察者”。这里要注意的一点是:主题来增加或删除观察者。 还是杂志订阅这个问题,我们想订阅杂志的时候,杂志出版社便会将我们加到它们的订阅名单里,我们不想订阅杂志时,杂志出版社便会将我们从订阅名单里删除。

    观察者模式:观察者模式定义了对象之间的一对多依赖(“一个主题”对“多个观察者”),这样一来,当一个对象改变状态时,它的所有依赖者(因为主题是真正拥有数据的人,观察者是主题的依赖者)都会收到通知并自动更新。

    实现代码如下:

    #include
    #include
    
    using namespace std;
    
    class Observer {  // 观察者
    public:
        virtual void update(float temp, float humidity, float pressure) = 0;
    };
    
    class Subject {  // 抽象主题
        virtual void registerObserver(Observer *o)=0;
        virtual void removeObserver(Observer *o)=0;
        virtual void notifyObserver()=0;
    };
    
    class DisplayElement {
        virtual void display()=0;
    };
    
    class WeatherData : public Subject  // 具象主题
    {
    private:
        vector<Observer*> observers;
        float temperature;
        float humidity;
        float pressure;
    public:
        void registerObserver(Observer *o)  // 注册观察者
        {
            observers.push_back(o);
        }
    
        void removeObserver(Observer *o)   // 取消观察者
        {
            auto it = std::find(observers.begin(), observers.end(), o);
    
            if (it != observers.end())
            {
                int index = std::distance(observers.begin(), it);
                cout << "索引是:" << index << endl;;
                observers.erase(observers.begin() + index);
                cout << "成功删除元素" << endl;
            }
            else
            {
                cout << "未找到元素" << endl;
            }
        }
    
        void notifyObserver()  // 通知观察者
        {
            for (int i = 0; i < observers.size(); i++)
            {
                Observer *observer = observers[i];
                observer->update(temperature, humidity, pressure);
            }
        }
        void measurementsChanged()
        {
            notifyObserver();  // 通知观察者
        }
    
        void setMeasurements(float temperature, float humidity, float pressure)
        {
            this->temperature = temperature; 
            this->humidity = humidity;
            this->pressure = pressure;
            measurementsChanged();
        }
    };
    
    class StatisticsDisplay : public Observer, public DisplayElement  // 观察者
    {
    private:
        float temperature;
        float humidity;
        WeatherData *weatherData;
    public:
        StatisticsDisplay(WeatherData *weather)
        {
            weatherData = weather;
            weatherData->registerObserver(this);  //主题注册观察者
        }
    
        void remove()
        {
            weatherData->removeObserver(this); // 主题取消观察者
        }
    
        void update(float temperature, float humidity, float pressure)
        {
            this->temperature = temperature;
            this->humidity = humidity;
            display();
        }
        void display()
        {
            cout << "statisticsDisplay: " << temperature << "F degress and " << humidity << "% humidity" << endl;
        }
    };
    
    class ForecastDisplay : public Observer, public DisplayElement  // 观察者
    {
    private:
        float temperature;
        float humidity;
        WeatherData *weatherData;
    public:
        ForecastDisplay(WeatherData *weather)
        {
            weatherData = weather;
            weatherData->registerObserver(this);  //主题注册观察者
        }
    
        void remove()
        {
            weatherData->removeObserver(this); // 主题取消观察者
        }
    
        void update(float temperature, float humidity, float pressure)
        {
            this->temperature = temperature;
            this->humidity = humidity;
            display();
        }
        void display()
        {
            cout << "ForecastDisplay: " << temperature << "F degress and " << humidity << "% humidity" << endl;
        }
    };
    
    class CurrentConditionsDisplay : public Observer, public DisplayElement  // 观察者
    {
    private:
        float temperature;
        float humidity;
        WeatherData *weatherData;
    public:
        CurrentConditionsDisplay(WeatherData *weather)
        {
            weatherData = weather;
            weatherData->registerObserver(this);  //主题注册观察者
        }
    
        void remove()
        {
            weatherData->removeObserver(this); // 主题取消观察者
        }
    
        void update(float temperature, float humidity, float pressure)
        {
            this->temperature = temperature;
            this->humidity = humidity;
            display();
        }
        void display()
        {
            cout << "CurrentConditionsDisplay: " << temperature << "F degress and " << humidity << "% humidity" << endl;
        }
    };
    int main()
    {
        WeatherData *weatherData = new WeatherData; // 定义一个主题对象即可
        CurrentConditionsDisplay currentDisplay(weatherData);  // 第一个观察者
        StatisticsDisplay statisDisplay(weatherData);   // 第二个观察者
        ForecastDisplay foreDisplay(weatherData);   // 第三个观察者
        weatherData->setMeasurements(80, 65, 30.4);   // 主题信息发生变更
        
        currentDisplay.remove();   // 该观察者取消对主题的订阅
    
        weatherData->setMeasurements(40, 25, 15.4);
    
        foreDisplay.remove();   // 该观察者取消对主题的订阅
    
        weatherData->setMeasurements(15.5, 26, 34);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178

    以上就是使用C++实现观察者模式的全部代码。

    设计原则

    1. 找出程序中会变化的方面,然后将其和固定不变的方面相分离。
      在观察中模式中,会改变的是主题的状态,以及观察者的数目和类型。用这个模式,你可以改变依赖于主题状态的对象,却不必改变主题。
    2. 针对接口编程,不针对实现编程。
      主题与观察者都是用接口:观察者利用主题的接口向主题注册,而主题利用观察者接口通知观察者。这样可以让两者之间运作正常,又同时具有松耦合的优点。

    观察者模式较为重要,在很多软件框架和软件设计中都可以看到它的身影,所以大家可以根据代码仔细体会它的思想。工作的那几个月在公司的软件里看到过观察者模式,但是没有自己动手实现,只是明白它的意思。今天自己动手实现了一下,感悟又深了一些。

  • 相关阅读:
    线程同步互斥机制
    独立站怎么设置打折活动?独立站新站怎么优化长尾词?——站斧浏览器
    C++入门基础05:表达式(表达式基础、算术运算符与赋值运算符、逻辑关系运算符、成员访问运算符与条件运算符、位运算符、移位运算符与类型转换)
    如何设计神经网络结构图,神经网络设计与实现
    面试问我线程池?还好我早有应对
    C#设计模式详解(2)——Factory Method(工厂方法)
    node.js
    轻松掌握组件启动之Redis单机、主从、哨兵、集群配置
    所有产品都值得用AI再做一遍,让AGI与品牌营销双向奔赴
    TiDB 工具适用场景
  • 原文地址:https://blog.csdn.net/qq_41596730/article/details/126231265