使用模板实现发布/订阅模式






4.71/5 (31投票s)
2002 年 11 月 29 日
4分钟阅读

183888

1102
利用 C++ 模板克服了传统发布/订阅设计模式的一些问题
引言
在著名的《设计模式》一书中描述的传统发布/订阅模式有一个恼人的缺点:在观察者(observer)的 update
方法中,观察者无法获得通知它的发布者(subject)的句柄。这意味着:
- 观察者需要存储一个指向它所关联的发布者的指针。
- 如果观察者关联了多个对象,它将无法确定是哪个对象通知了它。
本文将尝试利用 C++ 模板来解决这些问题。
原始设计模式
原始设计模式使用了两个基类:Subject
和 Observer
。Subject
基类包含存储所有已关联观察者的逻辑。Observer
类只包含一个纯 virtual
方法 (update()
),需要由继承 Observer
的 observer
类来填充。更多细节请阅读《设计模式》一书。Observer
类的 update()
方法不接受任何参数,这意味着继承自 Observer
的类不知道通知来自哪里。为 update
方法添加一个 'Subject'
参数并不难,但由于实际的发布者继承自 'Subject'
基类,所以观察类总是需要进行向下转型,这可能很危险。
解决方案
我们不定义两个基类,而是定义两个 template
类。这两个 template
类都将基于能够通知其他类(观察者)的 subject
类。
template <class T>
class Observer
{
public:
Observer() {}
virtual ~Observer() {}
virtual void update(T *subject)= 0;
};
这里的第一个改进是,我们的纯 virtual update
方法将 subject
的指针作为参数;不是(如下文所示的)基类 Subject
,而是作为参数传递给 template
定义的类。
template <class T>
class Subject
{
public:
Subject() {}
virtual ~Subject() {}
void attach (Observer<T> &observer)
{
m_observers.push_back(&observer);
}
void notify ()
{
std::vector<Observer<T> *>::iterator it;
for (it=m_observers.begin();it!=m_observers.end();it++)
(*it)->update(static_cast<T *>(this));
}
private:
std::vector<Observer<T> *> m_observers;
};
这里,我们定义了基本的 Subject
类/模板。attach
方法只是将 observer
(它是基本的 Observer<T>
类)添加到向量中。notify
方法只是通知所有观察者。这两个 template
可以在任何可以使用 Subject
/Observer
模式的情况下使用。以下类描述了它们的使用方式。
class Temperature : public Subject<Temperature>
{
public:
Temperature() {}
~Temperature() {}
void temperatureChanged () {notify();}
void getTemperature() {std::cout <<
" Getting the temperature." << std::endl;}
};
我们的 Temperature
类是一个监控温度的类,并在温度变化时通知其观察者。正如您所见,它所要做的就是调用 notify()
方法。getTemperature
方法只是在屏幕上写一些内容,但当然在实际情况中,它应该返回实际温度。看看 notify()
方法的实现。它只是调用所有已关联观察者的 update()
方法,并将自身作为参数传递。由于 'this
'(它是 Subject
<t> 类)被强制转换为 type T
,所以观察者的 update()
方法将获得正确的参数类型,如下例所示:
class PanicSirene : public Observer<Temperature>
{
public:
PanicSirene() {}
~PanicSirene() {}
void update (Temperature *subject)
{
std::cout << "Temperature was changed, sound the sirene"
<< std::endl;
subject->getTemperature();
}
};
正如您所见,一个指向触发通知的 Temperature
实例的指针作为参数传递给 update
方法。观察类(此处为 PanicSirene
)可以简单地调用通知发布者的任何方法,在此例中是 getTemperature
。以下源代码展示了它的实际用法:
Temperature temp;
PanicSirene panic;
temp.attach (panic);
temp.temperatureChanged ();
这将产生以下输出:
Temperature was changed, sound the sirene
Getting the temperature.
观察不同类型的多个发布者
如果您需要将观察者关联到多个对象,template
s 仍然易于使用。假设我们有一个类似的发布者类来测量压力。
class Pressure : public Subject<pressure>
{
public:
Pressure() {}
~Pressure() {}
void pressureChanged () {notify();}
void getPressure() {std::cout << " Getting the pressure."
<< std::endl;}
};
如果我们想在一个显示所有环境相关信息的窗口中同时显示 temperature
和 pressure
,我们只需像这样创建我们的 EnvironmentWindow
:
class EnvironmentWindow : public Observer<Temperature>,
public Observer<Pressure>
{
public:
EnvironmentWindow() {}
~EnvironmentWindow() {}
void update (Temperature *subject) {std::cout <<
"Temperature was changed" <<
std::endl; subject->getTemperature();}
void update (Pressure *subject) {std::cout <<
"Pressure was changed" <<
std::endl; subject->getPressure ();}
};
该类只需从 Observer
模板继承两次,分别用于 Temperature
和 Pressure
。请注意,这里有两个 update
方法,一个用于 temperature
,一个用于 pressure
。以下示例展示了如何使用它:
Temperature temp;
Pressure press;
EnvironmentWindow win;
PanicSirene panic;
temp.attach (win );
temp .attach (panic);
press.attach (win );
temp.temperatureChanged ();
press.pressureChanged ();
它显示了以下输出:
Temperature was changed
Getting the temperature.
Temperature was changed, sound the sirene
Getting the temperature.
Pressure was changed
Getting the pressure.
观察相同类型的多个发布者
如果我们的 PanicSirene
类需要同时验证内部温度和外部温度,我们不需要修改 PanicSirene
类的任何实现。我们只需将类实例关联到两个 Temperature
类。
Temperature internalTemp;
Temperature externalTemp;
PanicSirene panic;
internalTemp.attach (panic);
externalTemp.attach (panic);
许可证
本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。
作者可能使用的许可证列表可以在此处找到。