在进入技术细节之前,理解观察者模式(Observer Pattern)的基本概念和它在现代编程中的重要性是至关重要的。
观察者模式的定义
观察者模式是一种设计模式,它定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。在C++中,这个模式允许被观察者(Subject)以一种轻松的方式管理其观察者(Observers)的列表,并在状态改变时通知它们。
动态订阅与取消订阅
观察者模式的另一个核心原理是其支持动态订阅和取消订阅的能力。这意味着观察者可以在运行时决定是否要监听被观察者的状态变化,从而增加了系统的灵活性和适应性。
-
动态订阅的机制
在观察者模式中,观察者可以根据需要随时注册(订阅)或注销(取消订阅)自己对被观察者的监听。这是通过在被观察者中维护一个观察者列表来实现的,其中包括了所有当前对被观察者感兴趣的观察者。 -
实现动态订阅的策略
为了实现动态订阅和取消订阅,通常在被观察者中实现添加和移除观察者的方法。这些方法允许观察者在运行时加入或退出观察列表,从而实现对通知的动态控制。在C++中,这可以通过维护一个存储观察者指针或引用的容器(如 std::vector 或 std::list)来实现。
避免循环引用
在观察者模式中,另一个关键的考虑是避免循环引用,尤其是在使用现代C++编程语言时。循环引用会导致内存泄漏和对象生命周期管理的问题,因此理解并避免它们是至关重要的。
- 循环引用的定义及问题
循环引用发生在两个或多个对象互相持有对方的引用,从而形成一个闭环。在C++中,如果这些对象使用智能指针(如 std::shared_ptr)相互引用,可能导致引用计数永远不会降到零,从而阻止对象的正确析构。 - 使用 std::weak_ptr 避免循环引用
在观察者模式中,一种有效的避免循环引用的方法是使用 std::weak_ptr。std::weak_ptr 允许一个对象持有对另一个对象的引用,而不增加后者的引用计数。这意味着即使观察者和被观察者互相引用,也不会导致内存泄漏,因为它们不会阻止彼此的析构。 - 实例分析
例如,在一个事件处理系统中,一个事件发生器(被观察者)可能持有多个事件监听器(观察者)的引用。如果使用 std::shared_ptr,则可能导致循环引用。改用 std::weak_ptr,则可以确保当一个监听器不再需要时,可以被正确地销毁,同时不影响事件发生器或其他监听器。
通知机制的安全性
在观察者模式中,确保通知机制的安全性是至关重要的。当被观察者状态发生变化,需要通知其所有观察者时,必须保证这一过程不会导致程序崩溃或产生未定义行为,特别是在多线程环境中。
- 安全通知的挑战
在观察者模式中,当被观察者状态改变时,它会遍历其观察者列表并发送通知。这一过程中可能面临几个挑战:
- 观察者在接收通知后可能被销毁:这可能导致被观察者持有无效的观察者引用。
- 新的观察者在通知过程中注册:这需要被观察者动态管理观察者列表。
- 多线程环境下的同步问题:需要确保在多线程环境中通知的一致性和数据的完整性。
-
使用 std::weak_ptr 实现安全通知
为了确保通知的安全性,一个常见的做法是使用 std::weak_ptr。被观察者持有对观察者的 std::weak_ptr 引用,而非 std::shared_ptr。在发送通知之前,被观察者尝试将 std::weak_ptr 转换为 std::shared_ptr。如果转换成功,意味着观察者仍然存在,并且可以安全地接收通知。如果转换失败,意味着观察者已经不存在,可以安全地跳过。 -
线程安全和同步机制
在多线程环境中,还需要考虑线程安全和同步机制。这通常涉及锁定机制来确保在通知过程中观察者列表不会被并发修改。使用互斥锁(如 std::mutex)可以防止在发送通知时修改观察者列表,从而避免潜在的竞争条件和数据不一致。
C++实现观察者模式
-
使用 std::weak_ptr 和 std::shared_ptr
在C++中实现观察者模式时,智能指针——特别是 std::weak_ptr 和 std::shared_ptr——扮演了关键角色。它们不仅简化了内存管理,还提供了避免循环引用和保证通知机制安全性的有效手段。 -
std::shared_ptr 的作用
std::shared_ptr 是一种智能指针,它通过引用计数机制自动管理对象的生命周期。当一个 std::shared_ptr 被创建来指向一个对象时,该对象的引用计数增加;当 std::shared_ptr 被销毁或重新指向另一个对象时,引用计数减少。当引用计数降至零时,对象被自动销毁。
在观察者模式中,std::shared_ptr 可用于管理观察者对象,确保在观察者仍被需要时,它们不会被意外销毁。
3.1.2 std::weak_ptr 的重要性
std::weak_ptr 是另一种智能指针,它不拥有对象的所有权。它用来指向一个由 std::shared_ptr 管理的对象,但不增加该对象的引用计数。这意味着 std::weak_ptr 的存在不会阻止其所指向的对象被销毁。在观察者模式中,使用 std::weak_ptr 可以避免循环引用的问题。被观察者可以使用 std::weak_ptr 来引用观察者,这样即使观察者和被观察者相互引用,也不会导致内存泄漏。 -
实际应用中的权衡
在观察者模式中,被观察者(或“主题”)通常不需要“拥有”其观察者。这种设计的核心思想是:- 松耦合:观察者模式的一个主要目的是实现松耦合,即被观察者不需要知道观察者的具体实现细节,只需要知道它们实现了特定的接口或具有特定的方法。这样,观察者可以独立于被观察者存在和更改。
- 动态的订阅和取消订阅:观察者可以随时订阅或取消订阅被观察者的通知。这意味着观察者的存在和生命周期可能不与被观察者同步。例如,一个观察者可能在某个时间点选择取消订阅并被销毁,而不影响被观察者或其他观察者。
- 避免循环引用:在使用智能指针管理对象生命周期的场景中,被观察者使用 std::weak_ptr 引用观察者可以避免循环引用的问题。这是因为 std::weak_ptr 不会增加引用计数,因此不会阻止所指对象的正常销毁。
- 安全性:当被观察者需要通知其观察者时,它会尝试将每个观察者的 std::weak_ptr 转换为 std::shared_ptr。如果转换成功(表示观察者仍然存在),则进行通知。如果转换失败(表示观察者已经不存在),则跳过该观察者。这确保了通知过程中的安全性,避免了悬挂指针或无效引用的问题。
-
使用 std::weak_ptr 的观察者
当被观察者持有观察者的 std::weak_ptr 时,它不直接增加观察者的引用计数,这有助于防止循环引用。但是,当需要通知观察者时,被观察者需要将 std::weak_ptr 转换为 std::shared_ptr 才能安全地调用观察者上的方法。这是通过 std::weak_ptr 的 lock() 方法完成的。
#include <iostream>
#include <vector>
#include <memory>
class Observer {
public:
void notify() {
std::cout << "Observer notified." << std::endl;
}
};
class Observable {
public:
void addObserver(std::weak_ptr<Observer> observer) {
observers.push_back(observer);
}
void notifyObservers() {
for (auto &weakObserver : observers) {
if (auto observer = weakObserver.lock()) { // 将 weak_ptr 转换为 shared_ptr
observer->notify(); // 安全地调用观察者的方法
}
}
}
private:
std::vector<std::weak_ptr<Observer>> observers;
};
int main() {
auto observable = std::make_shared<Observable>();
auto observer1 = std::make_shared<Observer>();
auto observer2 = std::make_shared<Observer>();
auto observer3 = std::make_shared<Observer>();
observable->addObserver(observer1);
observable->addObserver(observer2);
observable->addObserver(observer3);
observable->notifyObservers();
return 0;
}
在这个例子中,当 Observable 类的 notifyObservers 方法被调用时,它会遍历所有观察者。对于每个观察者,它首
先尝试将 std::weak_ptr 转换为 std::shared_ptr。这是通过调用 weak_ptr 的 lock() 方法完成的。如果转换成功(即原始的 Observer 对象仍然存在),则 shared_ptr 是有效的,被观察者可以安全地调用观察者的 notify 方法。如果转换失败(即原始的 Observer 对象已经被销毁),则返回的 shared_ptr 为空,被观察者将不执行任何操作。
这种方法的优点是:
-
防止内存泄漏:通过避免循环引用,std::weak_ptr 帮助防止内存泄漏。
-
安全性:通过在调用之前检查 shared_ptr 的有效性,可以防止对悬挂指针的访问。
-
动态管理:观察者可以独立于被观察者创建和销毁。如果观察者不再存在,被观察者在通知时不会遇到问题。
总结来说,使用 std::weak_ptr 在观察者模式中是一种安全和有效的方法,既避免了循环引用,又保证了在通知观察者时的安全性和有效性。
观察者模式代码示例:
#include <iostream>
#include <string>
#include <list>
using namespace std;
class Subject;
//观察者 基类 (内部实例化了被观察者的对象sub)
class Observer {
public:
Observer(string name, Subject *sub) {
this->name = name;
this->sub = sub;
}
virtual void update() = 0;
protected:
string name;
Subject *sub;
};
class StockObserver : public Observer {
public:
StockObserver(string name, Subject *sub) : Observer(name, sub){}
void update();
};
class NBAObserver : public Observer {
public:
NBAObserver(string name, Subject *sub) : Observer(name, sub){}
void update();
};
class TaoBaoObserver : public Observer {
public:
TaoBaoObserver(string name, Subject *sub) : Observer(name, sub){}
void update();
};
//被观察者 基类 (内部存放了所有的观察者对象,以便状态发⽣变化时,给观察者发通知)
class Subject {
public:
string action; //被观察者对象的状态
virtual void attach(Observer *) = 0;
virtual void detach(Observer *) = 0;
virtual void notify() = 0;
protected:
std::list<Observer *> observers;
};
class Secretary : public Subject {
void attach(Observer *observer) {
observers.push_back(observer);
}
void detach(Observer *observer) {
list<Observer *>::iterator iter = observers.begin();
while (iter != observers.end()) {
if ((*iter) == observer) {
observers.erase(iter);
return;
}
++iter;
}
}
void notify() {
list<Observer *>::iterator iter = observers.begin();
while (iter != observers.end()) {
(*iter)->update();
++iter;
}
}
};
void StockObserver::update() {
cout << name << " 收到消息: " << sub->action << endl;
if (sub->action == "老板来了!") {
cout << "我马上关闭股票,装做很认真公作的样子! " << endl;
}
}
void NBAObserver::update() {
cout << name << " 收到消息: " << sub->action << endl;
if (sub->action == "老板来了!") {
cout << "我马上关闭 NBA,装做很认真公作的样子! " << endl;
}
}
void TaoBaoObserver::update() {
cout << name << " 收到消息: " << sub->action << endl;
if (sub->action == "老板来了!") {
cout << "我马上关闭淘宝,装做很认真公作的样子! " << endl;
}
}
int main()
{
Subject *BOSS = new Secretary();
Observer *xa = new NBAObserver("xa", BOSS);
Observer *xb = new TaoBaoObserver("xb", BOSS);
Observer *xc = new StockObserver("xc", BOSS);
BOSS->attach(xa);
BOSS->attach(xb);
BOSS->attach(xc);
BOSS->action = "老板走了! ";
BOSS->notify();
cout << endl;
BOSS->action = "老板来了!";
BOSS->notify();
return 0;
}