观察者模式
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
以下是模式的摘要:
-
对象(
DigitalClock
或AnalogClock
对象)使用 Subject 接口(Attach()
或Detach()
)作为观察者订阅(注册)或取消订阅(删除)自己作为观察者(subject.Attach(*this);
,subject.Detach(*this);
。 -
每个科目都可以有很多观察者(
vector<Observer*> observers;
)。 -
所有观察者都需要实现 Observer 接口。这个接口只有一个方法
Update()
,当 Subject 的状态改变时被调用(Update(Subject &)
) -
除了
Attach()
和Detach()
方法之外,具体主题还实现了Notify()
方法,该方法用于在状态发生变化时更新所有当前观察者。但在这种情况下,所有这些都是在父类Subject
(Subject::Attach (Observer&)
,void Subject::Detach(Observer&)
和void Subject::Notify()
。 -
Concrete 对象还可以具有设置和获取其状态的方法。
-
具体观察者可以是实现 Observer 接口的任何类。每个观察者订阅(注册)具有接收更新的具体主题(
subject.Attach(*this);
)。 -
观察者模式的两个对象是松散耦合的,它们可以相互作用但彼此知之甚少。
变异:
信号和插槽
信号和槽是 Qt 中引入的一种语言结构,它可以很容易地实现 Observer 模式,同时避免样板代码。概念是控件(也称为小部件)可以发送包含事件信息的信号,这些信息可以使用称为插槽的特殊功能由其他控件接收。Qt 中的槽必须是声明为此类的类成员。信号/插槽系统非常适合图形用户界面的设计方式。类似地,信号/槽系统可用于异步 I / O(包括套接字,管道,串行设备等)事件通知或将超时事件与适当的对象实例和方法或功能相关联。不需要编写注册/注销/调用代码,因为 Qt 的元对象编译器(MOC)自动生成所需的基础结构。
C#语言也支持类似的结构,尽管术语和语法不同:事件扮演信号的角色,代理是插槽。另外,委托可以是局部变量,很像函数指针,而 Qt 中的槽必须是声明为这样的类成员。