目录
观察者模式是我们日常用的比较多的一种行为设计模式,有时又被称为发布publish-订阅Subscribe模式、模型model-视图View模式、源-收听者Listener模式或从属者模式;在GOF的《设计模式:可复用面向对象软件的基础》一书中对观察者模式是这样说的:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。当一个对象发生了变化,关注它的对象就会得到通知;目标是通知的发布者,它发出通知时并不需要知道谁是它的观察者。
观察者模式UML类图如下:
Subject(目标):定义被观察者必须实现的职责,它必须能够动态地增加、取消观察者。它一般是抽象类或者是实现类,仅仅完成作为被观察者必须实现的职责,管理观察者并通知观察者。
Observer(观察者):?为那些在目标发生改变时需获得通知的对象定义一个更新接口。
ConcreteSubject(具体目标):将有关状态存入各ConcreteObserver对象;定义被观察者自己的业务逻辑,同时定义对哪些事件进行通知。
ConcreteObserver(具体观察者):存储有关状态,这些状态应与目标的状态保持一致;维护一个指向ConcreteSubject对象的引用;每个观察在接收到消息后的处理反应是不同,各个观察者有自己的处理逻辑。
观察者模式通过将主题和观察者解耦,实现了对象之间的松耦合。当主题的状态发生改变时,所有依赖于它的观察者都会收到通知并进行相应的更新。
主题和观察者实现如下:
class IObserver;
//抽象主题
class ISubject
{
public:
virtual ~ISubject() {}
virtual void add(IObserver* observer) = 0;
virtual void remove(IObserver* observer) = 0;
virtual void notify() = 0;
virtual int getState() const = 0;
virtual void setState(int state) = 0;
};
//抽象观察者
class IObserver
{
public:
virtual ~IObserver() {}
virtual void update(ISubject* pSubject) = 0;
};
//具体主题
class CConcreteSubject : public ISubject
{
public:
void add(IObserver* observer) override {
m_vecObservers.push_back(observer);
}
void remove(IObserver* observer) override {
m_vecObservers.remove(observer);
}
void notify() {
for (auto& it : m_vecObservers) {
it->update(this);
}
}
int getState() const override {
return m_state;
}
void setState(int state) override {
m_state = state;
notify();
}
private:
std::list<IObserver*> m_vecObservers;
int m_state;
};
//具体观察者1
class CConcreteObserver1 : public IObserver
{
public:
void update(ISubject* pSubject) override {
std::cout << "CConcreteObserver1 recv update, New value: " << std::to_string(pSubject->getState());
}
};
//具体观察者2
class CConcreteObserver2 : public IObserver
{
public:
void update(ISubject* pSubject) override {
std::cout << "CConcreteObserver2 recv update,New value:" << std::to_string(pSubject->getState());
}
};
//具体观察者3
class CConcreteObserver3 : public IObserver
{
public:
void update(ISubject* pSubject) override {
std::cout << "CConcreteObserver3 recv update,New value:" << std::to_string(pSubject->getState());
}
};
测试程序如下:
int main()
{
// Create Subject
ISubject* pSubject = new CConcreteSubject();
// Create Observer
IObserver* pObserver1 = new CConcreteObserver1();
IObserver* pObserver2 = new CConcreteObserver2();
IObserver* pObserver3 = new CConcreteObserver3();
pSubject->add(pObserver1);
pSubject->add(pObserver2);
pSubject->add(pObserver3);
// Change the state
pSubject->setState(2);
// Unregister the observer
pSubject->remove(pObserver1);
// Change the state
pSubject->setState(10);
// Unregister the observer
pSubject->remove(pObserver2);
pSubject->setState(3);
delete pObserver1;
delete pObserver2;
delete pObserver3;
delete pSubject;
return 0;
}
输出:
CConcreteObserver1 recv update, New value: 2
CConcreteObserver2 recv update, New value: 2
CConcreteObserver3 recv update, New value: 2
CConcreteObserver2 recv update, New value: 10
CConcreteObserver3 recv update, New value: 10
CConcreteObserver3 recv update, New value: 3
????????这个例子很简单,是一种经典的观察者模式实现,但这种实现不够通用,只能对特定的观察者才有效,即必须是 Obsrver 抽象类的派类才行,并且这个观察者类还不能带参数,虽然能在抽象类中定义带几个指定参数的观察方法,但这仍然不够用,因为在实际情况下参数个数是不定的。这种实现方式限定太多,主要的两个限定是:第一,需要继承,继承是强对象关系,不够灵活,第二,观察者被通知的接口参数不支持变化,导致观察者不能应付接口的变化。
????????我们可以通过 C++11 做一些改进,主要改进的地方有两个: 通过被通知接口参数化和std::function 来代替继承;通过可变参数模板和完美转发来消除接口变化产生的影响。改进之后的观察者模式和 C# 中的 event 类似,通过定义委托类型来限定观者者,不要求观察者必须从某个类派生,当需要和原来不同的观察者时,只需要定义一个新的 event?类型即可,通过event还可以方便地增加或者移除观察者。
????????我们还希望这个 event 类不可复制,要使类成为不可复制的,典型的实现方法是将类的复制构造丽数和赋值运算符设置为 private 或 proteted。如果二者都定义,那么编译器会提供一种作为公共成员函数的隐式版本。C++11 提供了 Defaut 和 Delete 函数,使我们可以更方便地实现一个NonCopyable 了,如果希望类不被复制,则直接从这个 onCopyable 派生即可。NonCopyable 的实现如代码:
class NonCopyable
{
protected:
NonCopyable() = default;
~NonCopyable()=default;
NonCopyable(const NonCopyable&)=delete;//禁用复制构造//禁用赋值构造
NonCopyable& operator= (const NonCopyable&) = delete;
}
#include <iostream>
#include <vector>
#include <map>
#include <functional>
#include <string>
using namespace std;
template<typename Func>
class Events
{
public:
Events(){};
~Events(){};
//注册观察者,支持右值引用
int Connect(Func&& f)
{
return Assign(f);
}
//注册观察者
int Connect(const Func& f)
{
return Assign(f);
}
//移除观察者
void Disconnect(int key)
{
m_connections.erase(key);
}
///通知所有观察者
template<typename ...Args>
void Notify(Args&& ...args)
{
for(auto& it : m_connections)
{
it.second(std::forward<Args>(args)...);
}
}
private:
///给观察者分配编号,在多线程环境需要加锁
template<typename F>
int Assign(F&& f)
{
int k = m_observerId++;
m_connections.emplace(k, std::forward<F>(f));
return k;
}
int m_observerId = 0;
std::map<int, Func> m_connections;
};
测试代码:
void print(int a, int b)
{
cout << "func " << a << ", " << b << endl;
}
class MyTest
{
public:
void print(int a, int b)
{
cout << "class " << a << ", " << b << endl;
}
};
int main()
{
Events<std::function<void(int, int)>> myevent;
auto key = myevent.Connect(print);
MyTest a;
std::function<void(int, int)> f = std::bind(&MyTest::print, &a, std::placeholders::_1, std::placeholders::_1);
auto key1 = myevent.Connect(f);
///lambda注册
auto lamkey = myevent.Connect([](int a, int b){cout << "lambda " << a << ", " << b << endl;});
myevent.Notify(1, 2);
return 0;
}
C++11实现的观察者模式,内部维护了一个泛型函数列表,观察者只需要将观察者函数注册进来即可,消除了继承导致的强耦合。通知接口使用了可变参数模板,支持任意参数,这就消除了接口变化的影响。
优点:
1)降低耦合度:观察者模式通过将主题和观察者之间的合关系抽象化为一种依赖关系,使得主题和观察者之间的耦合度降低,提高了代码的可维护性和复用性。
2)自动通知:观察者模式可以自动通知所有关联的观察者对象,避免了手动更新和回调的繁锁,提高了开发效率。
3)支持广播通信:观察者模式支持广播通信,即主题会将通知发送给所有关联的观察者,无论它们是否关心主题的状态变化;这有助于减少不必要的通信和计算。
缺点:
1)通知可能过于频繁:观察者模式可能会导致过多的通知,特别是在主题状态频繁变化的情况下,这会增加观察者的处理负担和系统的负担
2)通知可能不准确:观察者模式没有提供一种机制来保证所有观察者都能及时收到通知,因此可能会出现通知不准确的情况。例如,在某些情况下,一些观察者可能无法收到通知。
3)可能存在循环引用:在桌些情况下,观察者和主题之间可能存在循环引用,导致无法正常解耦。例如,一个观察者依赖主题赖的状态,而主题又依赖该观察者的行为
4)可能存在内存泄漏:如果观察著或主题对象持有集他对象的引用可能会导致内存泄漏。例如,一个观察者持有一个大对象的引用,而该对象又被其他对象引用,这样就会形成一个引用链,从而导致内存池漏。
5)观察者模式需要考虑一下开发效率和运行效率问题,一个被观察者,多个观察者,开发和调试就会比较复杂,而且在Java中消息的通知默认是顺序执行,一个观察者卡壳,会影响
整体的执行效率。在这种情况下,一般考虑采用异步的方式。多级触发时的效率更是让人担忧,大家在设计时注意考虑。