观察者模式
观察者模式是一种行为设计模式,主要用于实现一种订阅机制,可在目标事件发生变化时告知所有观察此事件的对象,使观察者做出对应的动作。通常是通过调用各观察者所提供的方法来实现。
UML类图
观察者模式
CObserver类,为观察者抽象类,为具体观察者定义标准接口:
- Update() 用于更新自身行为,由具体主题调用。
- GetName用于获取定义的字符,用于标识各个对象。
CSubject类,为主题抽象类,主要为具体的主题类定义标准的接口。主要接口:
- Register(): 注册观察者
- Unregister(): 注销指定观察者
- Notify(): 通知所有观察者
CRadio类,具体的主题类(CSubject子类)。例:无线电。
- 实现注册/注销观察当前主题的接口Register/Unregister,供观察者调用。
- 实现通知接口Notify,用于主题发布时,通知所有的观察者。
**CInterphone、CPhone **类,具体的观察者类。例:对讲机、手机
- 实现消息传递接口Update,供具体主题对象调用,用于通知主题内容。
场景列举
如上场景,现某处无线电在特定频道发出广播,所有关注此频道设备都会接收到此信号。
具体主题为Radio,其内部需要在主题发布时,通知所有观察者
代码语言:javascript复制class CRadio : public CSubject
{
public:
CRadio()
{
mObserverList.clear();
}
~CRadio()
{
}
int Register(CObserver *pObsr)
{
if (nullptr != pObsr) {
mObserverList.push_back(pObsr);
} else {
OBSERVER_LOGE("Register failed!n");
return -1;
}
return 0;
}
int Unregister(CObserver *pObsr)
{
if (nullptr != pObsr) {
mObserverList.remove(pObsr);
} else {
OBSERVER_LOGE("Unregister failed!n");
return -1;
}
return 0;
}
int Notify(void *pMsg)
{
list<CObserver *>::iterator it;
SMsgTransfer *pGmsg = (SMsgTransfer *)pMsg;
OBSERVER_LOG("Send radio: [0x%x] [%s]n", pGmsg->type, pGmsg->buf);
// loop: 向监听此事件的所有观察者发布主题
for (it = mObserverList.begin(); it != mObserverList.end(); it ) {
(*it)->Update(pMsg);
}
return 0;
}
private:
list <CObserver *> mObserverList;
};
具体观察者为可接收到此无线电的设备:手机、对讲机
代码语言:javascript复制/* 监听无线电的设备: 对讲机 */
class CInterphone : public CObserver
{
public:
explicit CInterphone(string value)
{
mName = value;
}
~CInterphone()
{
}
string GetName()
{
return mName;
}
int Update(void *pMsg)
{
SMsgTransfer *msg = (SMsgTransfer *)pMsg;
OBSERVER_LOG("%s receive: [0x%x] [%s]n", mName.c_str(), msg->type, msg->buf);
return 0;
}
private:
string mName;
};
/* 监听无线电的设备: 手机 */
class CPhone : public CObserver
{
public:
explicit CPhone(string value)
{
mName = value;
}
~CPhone()
{
}
string GetName()
{
return mName;
}
int Update(void *pMsg)
{
if (pMsg != NULL)
{
SMsgTransfer *msg = (SMsgTransfer *)pMsg;
OBSERVER_LOG("%s receive: [0x%x] [%s]n", mName.c_str(), msg->type, msg->buf);
} else {
OBSERVER_LOG("%s receive messages cannot be resolved!n", mName.c_str());
}
return 0;
}
private:
string mName;
};
客户端代码。使用Radio和设备形成应用场景
代码语言:javascript复制CRadio theRadio;
CPhone thePhoneUser1("Phone user1");
CPhone thePhoneUser2("Phone user2");
CInterphone theInterUser1("Interphone user1");
static int init()
{
// 注册监听无线电的设备,可放在观察者类初始化内部
theRadio.Register(&theInterUser1);
theRadio.Register(&thePhoneUser1);
theRadio.Register(&thePhoneUser2);
return 0;
}
int main(int argc, char *argv[])
{
init();
// 发出SOS无线电
OBSERVER_LOG("------------------------------------------n");
SMsgTransfer msg1 = {191519, "SOS"};
theRadio.Notify(&msg1);
OBSERVER_LOG("------------------------------------------n");
SMsgTransfer msg2 = {131313, "HELLO"};
theRadio.Notify(&msg2);
return 0;
}
输出:
代码语言:javascript复制$ ./a.out
------------------------------------------
Send radio: [0x2ec1f] [SOS]
Interphone user1 receive: [0x2ec1f] [SOS]
Phone user1 receive: [0x2ec1f] [SOS]
Phone user2 receive: [0x2ec1f] [SOS]
------------------------------------------
Send radio: [0x200f1] [HELLO]
Interphone user1 receive: [0x200f1] [HELLO]
Phone user1 receive: [0x200f1] [HELLO]
Phone user2 receive: [0x200f1] [HELLO]
总结
- 观察者将耦合转移到抽象类之间,实现耦合倒转,具体的实现类无需关注此耦合,只需要完成分内的工作即可。主题无需关注观察者的数量及行为,观察者也不需了解主题具体的逻辑。
- 满足开闭原则,当需要新增新的观察者时,只需要增加具体的观察者类即可,无需修改原有代码。
- 一般情况下,观察者会被应用于不同进程之间。例如,在电视机的系统中,电视需要待机。此时当发生待机动作时,所有需要在待机时做出相应动作的进程都需要关注待机事件(例:APP需要保存当前数据)。此场景下,待机就为主题,其他进程为观察者。且需要增加一个通知类来维护广播的机制,此类需要具备跨进程通信和观察者机制。
- 当系统中许多实例或组件需要关注同一个事件时,可采用观察者模式。