观察者模式

Observer Pattern 的目的是定义对象之间的一对多依赖关系,以便当一个对象更改状态时,将自动通知和更新其所有依赖关系。

主题和观察者定义了一对多关系。观察者依赖于主体,使得当主体的状态改变时,观察者得到通知。根据通知,观察者也可以使用新值进行更新。

以下是 Gamma 的设计模式一书中的例子。

#include <iostream>
#include <vector>

class Subject; 

class Observer 
{ 
public:
    virtual ~Observer() = default;
    virtual void Update(Subject&) = 0;
};

class Subject 
{ 
public: 
     virtual ~Subject() = default;
     void Attach(Observer& o) { observers.push_back(&o); }
     void Detach(Observer& o)
     {
         observers.erase(std::remove(observers.begin(), observers.end(), &o));
     }
     void Notify()
     {
         for (auto* o : observers) {
             o->Update(*this);
         }
     }
private:
     std::vector<Observer*> observers; 
};

class ClockTimer : public Subject 
{ 
public:

    void SetTime(int hour, int minute, int second)
    {
        this->hour = hour; 
        this->minute = minute;
        this->second = second;

        Notify(); 
    }

    int GetHour() const { return hour; }
    int GetMinute() const { return minute; }
    int GetSecond() const { return second; }

private: 
    int hour;
    int minute;
    int second;
}; 

class DigitalClock: public Observer 
{ 
public: 
     explicit DigitalClock(ClockTimer& s) : subject(s) { subject.Attach(*this); }
     ~DigitalClock() { subject.Detach(*this); }
     void Update(Subject& theChangedSubject) override
     {
         if (&theChangedSubject == &subject) {
             Draw();
         }
     }

     void Draw()
     {
         int hour = subject.GetHour(); 
         int minute = subject.GetMinute(); 
         int second = subject.GetSecond(); 

         std::cout << "Digital time is " << hour << ":" 
                   << minute << ":" 
                   << second << std::endl;           
     }

private:
     ClockTimer& subject;
};

class AnalogClock: public Observer 
{ 
public: 
     explicit AnalogClock(ClockTimer& s) : subject(s) { subject.Attach(*this); }
     ~AnalogClock() { subject.Detach(*this); }
     void Update(Subject& theChangedSubject) override
     {
         if (&theChangedSubject == &subject) {
             Draw();
         }
     }
     void Draw()
     {
         int hour = subject.GetHour(); 
         int minute = subject.GetMinute(); 
         int second = subject.GetSecond(); 

         std::cout << "Analog time is " << hour << ":" 
                   << minute << ":" 
                   << second << std::endl; 
     }
private:
     ClockTimer& subject;
};

int main()
{ 
    ClockTimer timer; 

    DigitalClock digitalClock(timer); 
    AnalogClock analogClock(timer); 

    timer.SetTime(14, 41, 36);
}

输出:

Digital time is 14:41:36
Analog time is 14:41:36

以下是模式的摘要:

  1. 对象(DigitalClockAnalogClock 对象)使用 Subject 接口(Attach()Detach())作为观察者订阅(注册)或取消订阅(删除)自己作为观察者(subject.Attach(*this);subject.Detach(*this);

  2. 每个科目都可以有很多观察者(vector<Observer*> observers;)。

  3. 所有观察者都需要实现 Observer 接口。这个接口只有一个方法 Update(),当 Subject 的状态改变时被调用(Update(Subject &)

  4. 除了 Attach()Detach() 方法之外,具体主题还实现了 Notify() 方法,该方法用于在状态发生变化时更新所有当前观察者。但在这种情况下,所有这些都是在父类 SubjectSubject::Attach (Observer&)void Subject::Detach(Observer&)void Subject::Notify()

  5. Concrete 对象还可以具有设置和获取其状态的方法。

  6. 具体观察者可以是实现 Observer 接口的任何类。每个观察者订阅(注册)具有接收更新的具体主题(subject.Attach(*this);)。

  7. 观察者模式的两个对象是松散耦合的,它们可以相互作用但彼此知之甚少。

变异:

信号和插槽

信号和槽是 Qt 中引入的一种语言结构,它可以很容易地实现 Observer 模式,同时避免样板代码。概念是控件(也称为小部件)可以发送包含事件信息的信号,这些信息可以使用称为插槽的特殊功能由其他控件接收。Qt 中的槽必须是声明为此类的类成员。信号/插槽系统非常适合图形用户界面的设计方式。类似地,信号/槽系统可用于异步 I / O(包括套接字,管道,串行设备等)事件通知或将超时事件与适当的对象实例和方法或功能相关联。不需要编写注册/注销/调用代码,因为 Qt 的元对象编译器(MOC)自动生成所需的基础结构。

C#语言也支持类似的结构,尽管术语和语法不同:事件扮演信号的角色,代理是插槽。另外,委托可以是局部变量,很像函数指针,而 Qt 中的槽必须是声明为这样的类成员。