一、什么是观察者模式
观察者模式是一种非常普遍又很实用的开发设计模式,c++不像其他高级语言一样有现成的标准封装库,所以在c++语言中只能开发者自己来设计出一种观察者模式。从技术上来说,一个正确“观察者”是一个比较复杂的设计过程。
在观察者设计模式结构中有两种基本角色,观察者和被观察者;被观察者和观察者是一对多的关系,简单来说就是一个被观察者可以被多个观察者监听。当被观察者中有信号变化时就会通知那些观察者这种信号的变化,这样观察者就能在不轮询的情况下依然能实时了解到所关注信号的变化。具体的对应关系如下图所示:
二、观察者模式的使用场景
1, 当一个对象的信号变化需要被多个其他对象所知道的时候,但是又不清楚有多少个其他对象是关注这种信号变化的,此时就适合用观察者模式来设计逻辑
2,当某一个复杂的功能需要多个类对象来共同完成的时候,为了不让这些类相互循环包含调用耦合在一起,用观察者模式就非常有利于模块间的合作解耦。
三、观察者模式的实现
3-1:普通观察者模式实现
- ObserverBase(观察者基类): 这是一个抽象类,提供一个update的纯虚函数,为所有继承该类的具体观察者提供一个通用的接口,通过update接口能拿到最新的消息数据
- SubjectBase(主题 基类): 这是一个抽象类,该类提供三个纯虚函数 (消息注册接口Subsribe 和、消息注销接口UnSubsribe、通知接口Notify)。继承该类的具体主题实现这三个接口,并且可以管理消息ID和观察者的关系。有需要更新的事件时通过Notify来通知注册过该消息的观察者。
- ConcreteObserver(具体业务的观察者):实现update这个纯虚接口,通过这个接口获取实时更新的数据,然后实现具体的业务逻辑。
- ConcreteSubject(具体的消息主题): 实现SubjctBase中的纯虚函数,并且将消息ID和观察作者做一个订阅绑定。当有业务消息需要通知观察者时,通过Notify来通知对应的观察者对象。
基本框图如下:
实现代码如下:
#include <list>
#include <vector>
#include <map>
using namespace std;
//观察者抽象基类
class ObserverBase
{
public:
ObserverBase(){}
~ObserverBase(){}
virtual void Update(int message) = 0; //需要子类实现的信号更新函数
};
class SubjectBase
{
private:
/* data */
public:
SubjectBase(){}
~SubjectBase(){}
virtual void Subsribe(int message, ObserverBase * observer) = 0; //观察者订阅指定类型事件
virtual void UnSubsribe(int message, ObserverBase * observer) = 0; //观察者取消订阅指定类型事件
virtual void Notify(int message) = 0; //通知已订阅指定事件的观察者
protected:
map<int, vector<ObserverBase *>> m_observers_;
};
class MyObserver : public ObserverBase
{
public:
explicit MyObserver(const std::string &str) : name_(str)
{}
void Update(int message) override
{
std::cout << name_ << " Received message";
std::cout << message << std::endl;
}
private:
std::string name_;
};
class MySubject: public SubjectBase
{
private:
/* data */
public:
enum E_MSG_ID {ADD, REMOVE}; // 消息类型
MySubject(/* args */);
~MySubject();
//观察者订阅指定类型事件
void Subsribe(int message, ObserverBase * observer) override
{
if (observer) {
auto it = m_observers_.find(message);
if (it == m_observers_.end()) {
vector<ObserverBase *> list;
list.push_back(observer);
m_observers_[message] = list;
} else {
it->second.push_back(observer);
}
}
}
void UnSubsribe(int message, ObserverBase * observer) override
{
auto it = m_observers_.find(message);
if (it != m_observers_.end()) {
for(auto it1 = it->second.begin(); it1 != it->second.end(); ++it1)
{
if(observer == (*it1))
{
it1 = it->second.erase(it1);
return;
}
}
}
}
void Notify(int message) override
{
auto it = m_observers_.find(message);
if (it != m_observers_.end() && !it->second.empty()) {
for (auto obj : it->second) {
if (obj) {
obj->Update(message);
}
}
}
}
};
这种普通的观察者模式,一个观察者只能观察一类主题,如果需要观察多种主题的时候就需要声明多个观察者,这是非常冗余的代码,阅读和维护起来也比较困难,所以基于此基本思想的迭代,就有了下面的基于模板的观察者模式。
3-2:基于模板的观察者模式实现
实现代码如下:
#include <string>
#include <vector>
#include <iostream>
#include <mutex>
//观察者
template <typename T>
class Observer{
public:
Observer(){}
virtual void filed_update(T&source, const std::string & filed_name) = 0;
};
//观察主题
//1:依赖属性的问题要注意; 2:线程安全的问题通过锁解决; 3:可重入的问题,比如Notify Subscribe Unsubscribe三个接口相互调用的情况
template <typename T>
class Subject{
public:
Subject(){}
void Notify(T&source, const std::string & name)
{
std::vector<Observer<T> *> observers_copy;
{
std::lock_guard<std::mutex> lock(mtx); //加锁解决线程安全问题
observers_copy = m_observers;
}
for(auto obs : observers_copy)
{
if(obs)
{
obs->filed_update(source, name);
}
}
}
void Subsribe(Observer<T> *f)
{
std::lock_guard<std::mutex> lock(mtx);
auto it = std::find(m_observers.begin(), m_observers.end(), f);//同一个对象只允许订阅一次
if(it == m_observers.end())
{
m_observers.push_back(f);
}
else
{
std::cout << "The Observer exist" << std::endl;
}
}
void UnSubscribe(Observer<T> *o)
{
std::lock_guard<std::mutex> lock(mtx);
auto it = std::find(m_observers.begin(), m_observers.end(), o);
if(it != m_observers.end())
{
m_observers.erase(it);
}
}
private:
std::vector<Observer<T> *> m_observers;
std::mutex mtx;
};
class Person: public Subject<Person>
{
public:
Person(int age) : age(age){};
void set_age(const int age)
{
if(age == this->age) return;
this->age = age;
Notify(*this, "age");
}
void set_id(int id)
{
if(id == this->id) return;
this->id = id;
Notify(*this, "id");
}
int get_age(){return age;}
int get_id(){return id;}
private:
int age;
int id;
};
class ConsolePersonObserver : public Observer<Person>
{
void filed_update(Person& source, const std::string & filed_name) override
{
if(filed_name == "age"){
std::cout << "Person's " << filed_name << " has changed to " << source.get_age() << std::endl;
}
else if(filed_name == "id"){
std::cout << "Person's " << filed_name << " has changed to " << source.get_id() << std::endl;
}
}
};
测试代码如下:
#include "observer.h"
int main(int argc, char *argv[])
{
Person p(20);
ConsolePersonObserver cpo;
p.Subsribe(&cpo);
p.set_age(21);
p.set_age(22);
p.set_id(21);
p.set_id(22);
return 0;
}
这种模式一个主题可以让多个观察者来订阅,不需要多余的代码来兼容,是比较容易维护的。最终用哪种都是取决于业务的需求,没有绝对的高低之分。
四、观察者模式的优缺点
优点: 被观察的主题(Subject)不需要知道具体观察者(MyObserver)的实现和接口,实现完成解耦,只需要知道观察者抽象类(ObserverBase)的抽象接口(update)。事先为观察者对象订阅其所关注的消息类型,当该消息类型有变化时通过被观察者的抽象接口(Notify)通知观察者即可,这样具体的观察者和具体的被观察者也实现完全解耦。
缺点: 性能上有一点的损耗,有消息变化时必须遍历列表去找到订阅其的观察者,在这点上有一点的性能损耗,对于大数据量较大对响应实时性要求又高的系统要慎重使用。