分享

观察者模式 Observer Pattern

 阿修罗之狮猿授 2016-04-18

博客来源:http://blog.csdn.net/haimian520/article/details/51143513
之前总是想写一个程序,大体上是这样的:单服务器,多客户端。服务器上比如说是个房屋价格信息,客户端则是租房子,显示价格。如果现在有特价,怎么客户端怎么实时获取?而且作为客户端,用户定是不止一个,如何通知所有的客户端呢?直到前天才发现,这是另一个模式—-观察者模式中讲解的 内容。在看这个之前已经心中有底了,客户端数目是变化的,可以用向量vector;通知所有客户端,for循环。

观察者模式:

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

主题对象管理某些数据。
当主题内的数据改变,就会通知观察者。
观察者已经订阅(注册)主题以便在主题数据改变时能够收到更新。

鸭子对象不是观察者,鸭子对象过来告诉主题,它想当一个观察者。
鸭子对象要注册(订阅)主题对象。
然后鸭子就已经是正式的观察者了。 (此 鸭子 非 前面的策略模式里面的鸭子)
主题有了新的数据值!
现在鸭子和其他所有观察者都会收到通知 – 》 主题已经改变了。

定义观察者模式:类图

依赖:
主题具有状态;观察者使用这些状态。
观察者依赖主题来告诉他们状态何时改变了(主题调用观察者的方法。他们不在一个类中怎么办?把所有的观察者对象存放到主题的变量中去,然后for循环调用观察者对象的 update方法);
主题是真正拥有数据的人,观察者是主题的依赖者,在数据变化时更新。
关于观察者的一切,主题只知道观察者实现了某个接口(也就是Observer接口)。主题不知道 也 不需要知道观察者的具体类是谁、做了些什么或其他任何细节。 – 》 松耦合

松耦合:当两个对象之间松耦合,可以在不知道彼此间的细节的前提下,进行交互。

对于下面的 气象监测应用

WeatherData这个主题(Subject)存储温度、湿度、气压等天气状况。当属性发生改变时,会通知所有的显示装置(Observer观察者)。

对于显示装置,可能会不同,比如:

每个布告板都有差异,因此我们需要一个共同的接口。尽管布告板的类都不一样,但是它们都应该实现相同的接口,好让WeatherData对象能够知道如何把观测值送给它们。所以每个应该有一个update方法,供WeatherData对象调用。

观察者:

class Observer
{
public:
    virtual void update(float t, float p, float h ){}    
};
class CurrentObserver :public Observer, public Display
{
    float temperature;// 只对温度感兴趣
public:
    void update(float t, float p, float h){
        temperature = t;
    }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

主题:

class Subject
{
public:
    // 主题保存一个观察者列表,这样可以通知所有的观察者。
    // 但是呢,这个列表的元素怎么来呢?通过注册观察者方法来添加        
    // 理所应当的是,该函数传入的是一个观察者对象。
    virtual void registerObserver(Observer *o){}
    virtual void removeObserver(Observer *o){}
    virtual void notifyObserver(){}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
class WeatherData :public Subject
{
    floattemperature;          // 特定的Subject主题有温度、湿度、压强三个数据
    float humidity;
    float pressure; 

    vector <Observer*> observerLst;  // 存放所有的观察者(指针)

    void registerObserver(Observer *o)
    {
        observerLst.push_back(o);
    }

    void removeObserver(Observer *o)   
    {   
        observerLst.erase(index of which element is equivalent to o);  
    } 

    void notifyObservers()        
    {  
        for each  observer.update(t, p, h);    
    };

};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

然后呢,Subject类属性改变时,需要通知所有观察者。
改变属性:

setValue(float t, float p , float h)
{ 
    temperature = t; 
    humidity = p; 
    pressure = h;          // 修改此类(WeatherData )的数据 
    notifyObservers();     // 通知所有观察者 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

对于观察者怎么写呢?
首先观察者每个的显示方式不同,在前面有了个DisplayElement这个接口,封装了不同的变化。在特定类型的观察者需要实现DisplayElement里面的display方法即可。

class Display
{
public:
    virtual void display(){};
};
  • 1
  • 2
  • 3
  • 4
  • 5

所以:

class ConcreteObserver:public Observer , public DisplayElement 
{ 
    // 不的兴趣 –》 只对温度感兴趣
    float temperature;
    // 不同的显示方式 --> DisplayElement
    void display(){ /*...........*/  }
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

然后就有:

class ConcreteObserver:public Observer , public DisplayElement 
{ 
    ConcreteObserver() // 构造函数 
    { 
    }

    void update(/*有温度,气压,湿度三个参数*/)
    {
        // 当Subject类的数据发生改变时,通知所有的观察者更新数据
        // 有个小缺点:
        // Subject类将所有的数据都撂给了观察者,而观察者只挑拣他们感兴趣的东西(有点浪费)
        // 比如:ConcreteObserver这个类只对 温度 感兴趣,如下:

        this-> temprature = t;      // 该类有一个属性float temperature;

        // 而这个update函数因为要统一函数签名,让所有的观察者都有相同的函数。
        // 以便在Subject中以相同的方式传出所有数据,所以: 
        // void update(float temp, float pressure, float humidity) 
        // 也就是Observer中update那样写的原因

        // 该类数据更新了,为了能够看得到,或者说是即时显示,则需要在这里调用显示方法:
        display();      // 显示一下最新数据
    }   

    void display(){ /*.........*/ }    // 实现DisplayElement接口的display方法。
};
  • 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

对于构造函数: ConcreteObserver()传入的参数又是Subject *weatherData。
在注册(订阅)主题时:

weatherData->registerObserver(this);
  • 1

this是指它本身。在创建对象时,它将类本身ConcreteObserver创建的对象传给了Subject类型的WeatherData了的对象了。

如何将气象观测值放到布告板上?
在观察者模式中,如果我们把WeatherData对象当作主题,把布告板当作观察者,布告板为了取得信息,就必须先向WeatherData对象注册。

class CurrentObserver : public Observer
{
private:
    Subject *weatherData;
public:
    CurrentObserver()
    {
        weatherData->registerObserver(this);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

你会发现上面的CurrentObserver()构造函数并不能满足需要:
因为首先weatherData是个指针,要指向一个确切的东西。不然weatherData 是个 空指针,怎么对它进行操作呢。
在下面的main函数中,创建了一个WeatherData对象,将其地址传给了CurrentObserver

WeatherData *wd = new WeatherData();    
CurrentObserver *fo = new CurrentObserver(wd);
  • 1
  • 2

相应的构造函数改为:

CurrentObserver(Subject *weatherData) // 使用基类的Subject类型指针,适用于所有主题Subject 
{
    temperature = 0.0f;
    weatherData->registerObserver(this); // 构造函数,在这里调用Subject类的registerObserver方法,
                                         // 将该类的实例加入Subject类的List中去。
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

不知道你们看懂了没?反正我不知道我在说啥。

C++代码:

    #include<iostream>
    #include<vector>

    using namespace std;

    class Display
    {
    public:
        virtual void display(){};
    };

    class Observer
    {
    public:
        virtual void update(float t, float p, float h ){}    
    };


    class Subject
    {
    public:
        virtual void registerObserver(Observer *o){}
        virtual void removeObserver(Observer *o){}
        virtual void notifyObserver(){}
    };

    // ConcreteSubject 
    class WeatherData :public Subject
    {
    private:
        float temp;
        float pressure;
        float hum;
        vector <Observer*> observerLst;
    public:
        WeatherData()
        {
            temp = 0.0f;
            pressure = 0.0f;
            hum = 0.0f;
        }

        void registerObserver(Observer *o)
        {
            observerLst.push_back(o);
        }
        void removeObserver(Observer *o)
        {

            for (int i = 0; i < int(observerLst.size()); ++i)
            {
                if (o == observerLst[i])
                {
                    observerLst.erase(observerLst.begin() + i);
                    break;
                }
            }
        }
        void setValue(float t, float p, float h)
        {
            temp = t;
            pressure = p;
            hum = h;

            notifyObserver();
        }
        void notifyObserver()
        {
            for (int i = 0; i < int(observerLst.size()); ++i)
            {
                (*observerLst[i]).update(temp, pressure,hum);
            }
        }
    };

    // ConcreteObserver
    class CurrentObserver :public Observer, public Display
    {
    private:
        float temperature;
        Subject *weatherData;
    public:
        CurrentObserver(Subject *weatherData)
        {
            //temperature = 0.0f;
            this->weatherData = weatherData;            
            weatherData->registerObserver(this);
            weatherData->notifyObserver();
        }
        void update(float t, float p, float h){
            temperature = t;
            display();
        }
        void display()
        {
            cout << "CurrentObserver: "
                << "temperature  : " << temperature 
                << endl;
        }
    };

    int main()
    {
        WeatherData *wd = new WeatherData();

        CurrentObserver *fo = new CurrentObserver(wd);

        cout << "fo->display(): ";
        fo->display();

        wd->setValue(10, 10, 10);

        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

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

松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低。

The observer pattern is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods. It is mainly used to implement distributed event handling systems. The Observer pattern is also a key part in the familiar model–view–controller (MVC) architectural pattern.[1] The observer pattern is implemented in numerous programming libraries and systems, including almost all GUI toolkits.

观察者模式 : Subject 维护一个观察者依赖列表。如果Subject的状态发生改变并自动的通知他们,通常通过调用观察者的方法。主要用在实现分布式事件处理系统。观察者模式也是大家熟悉的模型 - 视图 - 控制器(MVC)架构模式的一个关键组成部分。观察者模式在编程库和系统中有大量的实现,包括几乎所有的GUI工具。

We can not talk about Object Oriented Programming without considering the state of the objects. After all object oriented programming is about objects and their interaction. The cases when certain objects need to be informed about the changes occured in other objects are frequent. To have a good design means to decouple(尽可能的去耦合) as much as possible and to reduce the dependencies(降低依赖). The Observer Design Pattern can be used whenever a subject has to be observed by one or more observers.
当另一个对象频繁的发生变化时,该对象需要被频繁通知。 观察者模式可以被用在 主题 被一个或者多个 观察者 观察的任何情况。

这里写图片描述
图中:实线箭头 依赖; 虚线箭头 实现;
ConcreteSubject维护一个观察者列表;
ConcreteObserver的构造函数中

Subject *weatherData;
CurrentObserver(Subject *weatherData)
{
    this->weatherData = weatherData;
    weatherData->registerObserver(this);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

还有一个网站:http:///observer.html
很好的网站,讲了一下引擎实现的原型。

它对Observer Pattern进行分析:
太慢?不是的,它并不是事件订阅,消息机制或者数据绑定,发送数据仅仅是遍历一下列表,然后调用一些虚方法。

太快?不是的,慢的观察者会阻塞Subject的执行。如UI编程,解决方法:放到另一个线程当中或者一个工作队列中(queue)。

the observer registers with the subject, expressing its interest in observing. When a state change occurs, the subject notifies the observer of the change. When the observer no longer wishes to observe the subject, the observer unregisters from the subject. These steps are known as observer registration, notification, and unregistration, respectively.

  1. Observer Registration:

    observer invokes the Register method on the subject, passing itself as an argument.
    Once the subject receives this reference, it must store it in order to notify the observer when a state change occurs sometime in the future. Rather than storing the observer reference in an instance variable directly, most observer implementations delegate this responsibility to a separate object, typically a container. Use of a container to store observer instances provides important benefits that we will discuss shortly. With that in mind, the next action in the sequence is the storage of the observer reference denoted by the invocation of the Add method on the container.
    UML sequence diagrams:
    这里写图片描述

    再来一个:
    这里写图片描述
    (Basic Observer interaction)

  2. Observer Notification:

    When a state change occurs (AskPriceChanged), the subject retrieves all the observers within the container by invoking the GetObservers method. The subject then enumerates through the retrieved observers, calling the Notify method, which notifies the observer of the state change.
    这里写图片描述

在前面了解到:当Subject对象发生变化是,Subject会把所有的信息都撂给所有的Observer,有Observer来决定挑选哪些信息。这就是Push model;这会导致传送信息量过大,而且在传送的时候,决定传送哪些信息会出错。
Push model

In the push model, the client sends all relevant information regarding the state change to the subject, which passes the information on to each observer. If the information is passed in a neutral format (for example, XML), this model keeps the dependent observers from having to access the client directly for more information. On the other hand, the subject has to make some assumptions about which information is relevant to the observers. If a new observer is added, the subject may have to publish additional information required by that observer. This would make the subject and the client once again dependent on the observers - the problem you were trying to solve in the first place. So when using the push model, you should err on the side of inclusion when determining the amount of information to pass to the observers. In many cases, you would include a reference to the subject in the call to the observer. The observers can use that reference to obtain state information.
还有一种Pull model
In the pull model, the client notifies the subject of a state change. After the observers receive notification, they access the subject or the client for additional data (see Figure 5) by using a getState() method. This model does not require the subject to pass any information along with the update() method, but it may require that the observer call getState() just to figure out that the state change was not relevant. As a result, this model can be a little more inefficient(但是低效).

Another possible complication occurs when the observer and the subject run in different threads (for example, if you use RMI to notify the observers). In this scenario, the internal state of the subject may have changed again by the time the observer obtains the state information through the callback. This may cause the observer to skip an operation.不懂
这里写图片描述

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约