监听者模式(Observer Pattern)是一种行为设计模式,它定义了对象之间的一对多依赖关系。当一个对象的状态发生变化时,所有依赖于它的对象都会收到通知并自动更新。这种模式非常适合用于事件驱动的系统,例如 GUI 框架、消息队列等。
在本文中,我们将通过编写一个简单的监听者模式类 Observable
,来学习如何实现这一设计模式。
1. 监听者模式的核心概念
监听者模式通常包含以下两个核心组件:
-
Subject(主题):维护一个观察者列表,并提供注册、注销和通知观察者的接口。
-
Observer(观察者):定义一个更新接口,用于接收主题的通知。
在我们的实现中,Observable
类充当 Subject 的角色,而观察者是一个 std::function<void()>
回调函数。
2. 实现 Observable
类
以下是 Observable
类的完整实现:
#ifndef __OBSERVABLE_H___
#define __OBSERVABLE_H___
#include <functional>
#include <mutex>
#include <unordered_map>
#include <cassert>
namespace cise {
/**
* @class Observable
* @brief 观察者模式中的可观察对象,允许观察者注册、注销和接收通知。
*
* @tparam Event 观察事件的类型,可以是任意可哈希的类型(如枚举、整数、字符串等)。
*/
template<typename Event>
class Observable final {
public:
using Handle = int; // 观察者句柄类型
public:
Observable() : nextHandle_(0) {}
/**
* @brief 添加观察者。
*
* @param event 观察事件的键值。
* @param callback 观察者的回调函数。
* @return 返回观察者的句柄,用于后续注销。
*/
Handle addObserver(Event event, std::function<void()> callback) {
std::lock_guard<std::mutex> lock(mutex_);
// 检查 Handle 是否溢出
assert(nextHandle_ < std::numeric_limits<Handle>::max() && "Handle overflow: maximum number of observers reached.");
auto& handlers = observers_[event];
handlers[nextHandle_] = std::move(callback);
return nextHandle_++;
}
/**
* @brief 移除指定事件和句柄的观察者。
*
* @param event 观察事件的键值。
* @param handle 观察者的句柄。
*/
void removeObserver(Event event, Handle handle) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = observers_.find(event);
if (it != observers_.end()) {
it->second.erase(handle);
}
}
/**
* @brief 移除所有事件下指定句柄的观察者。
*
* @param handle 观察者的句柄。
*/
void removeObserver(Handle handle) {
std::lock_guard<std::mutex> lock(mutex_);
for (auto& [event, handlers] : observers_) {
handlers.erase(handle);
}
}
/**
* @brief 通知所有观察指定事件的观察者。
*
* @param event 观察事件的键值。
* @note 如果某个观察者的回调函数抛出异常,异常会直接传递给调用者。
*/
void notifyObservers(Event event) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = observers_.find(event);
if (it != observers_.end()) {
for (auto& [handle, callback] : it->second) {
callback(); // 异常会直接传递给调用者
}
}
}
private:
// 使用 std::unordered_map 而不是 std::map,因为90%场景不需要按事件键值有序遍历观察者,有利于提高性能。
std::unordered_map<Event, std::unordered_map<Handle, std::function<void()>>> observers_;
Handle nextHandle_;
std::mutex mutex_;
};
} // namespace cise
#endif // __OBSERVABLE_H___
3. 设计亮点
3.1 线程安全
使用 std::mutex
保护共享资源,确保在多线程环境下安全地添加、移除和通知观察者。
3.2 高性能
使用 std::unordered_map
存储观察者,避免了有序遍历的开销,提高了性能。
3.3 灵活性
支持任意可哈希的事件类型(如枚举、整数、字符串等),适用于多种场景。
3.4 异常处理
将异常直接传递给调用者,遵循"谁调用,谁处理"的原则。
4. 使用示例
以下是一个简单的使用示例:
#include <iostream>
#include "Observable.h"
int main() {
cise::Observable<std::string> observable;
// 添加观察者
auto handle1 = observable.addObserver("event1", []() {
std::cout << "Observer 1: Event 1 triggered!" << std::endl;
});
auto handle2 = observable.addObserver("event2", []() {
std::cout << "Observer 2: Event 2 triggered!" << std::endl;
});
// 通知观察者
observable.notifyObservers("event1"); // 输出: Observer 1: Event 1 triggered!
observable.notifyObservers("event2"); // 输出: Observer 2: Event 2 triggered!
// 移除观察者
observable.removeObserver("event1", handle1);
observable.notifyObservers("event1"); // 无输出,观察者已移除
return 0;
}
5. 单元测试
为了确保 Observable
类的正确性,我们编写了以下单元测试:
#include <gtest/gtest.h>
#include "Observable.h"
using namespace cise;
TEST(ObservableTest, AddAndNotifyObserver) {
Observable<std::string> observable;
bool isCalled = false;
auto handle = observable.addObserver("event1", [&isCalled]() {
isCalled = true;
});
observable.notifyObservers("event1");
EXPECT_TRUE(isCalled);
}
TEST(ObservableTest, RemoveObserverByEvent) {
Observable<std::string> observable;
bool isCalled = false;
auto handle = observable.addObserver("event1", [&isCalled]() {
isCalled = true;
});
observable.removeObserver("event1", handle);
observable.notifyObservers("event1");
EXPECT_FALSE(isCalled);
}
TEST(ObservableTest, NotifyWithException) {
Observable<std::string> observable;
observable.addObserver("event1", []() {
throw std::runtime_error("Callback failed");
});
EXPECT_THROW(observable.notifyObservers("event1"), std::runtime_error);
}
Makefile:
# 编译器
CXX = g++
# 编译选项
CXXFLAGS = -std=c++11 -Wall -Wextra -g -pthread
# Google Test 路径(根据你的安装路径修改)
GTEST_DIR = /path/to/gtest
# 目标文件
TARGET = observable_test
# 源文件
SRCS = ObservableTest.cpp Observable.h
# 编译规则
$(TARGET): $(SRCS)
$(CXX) $(CXXFLAGS) -isystem $(GTEST_DIR)/include -I$(GTEST_DIR) \
$(GTEST_DIR)/libgtest.a $(GTEST_DIR)/libgtest_main.a \
$(SRCS) -o $(TARGET)
# 运行测试
test: $(TARGET)
./$(TARGET)
# 清理
clean:
rm -f $(TARGET)
示例目录结构
假设你的项目目录结构如下:
复制
/project
├── Observable.h
├── ObservableTest.cpp
├── Makefile
└── /gtest (Google Test 安装路径)
运行示例
-
编译:
make
-
运行测试:
make test
-
清理:
make clean
6. 总结
通过本次编程练习,我们实现了一个简单但功能强大的监听者模式类 Observable
。它不仅支持多线程环境,还具有高性能和灵活性。希望这篇文章能帮助你更好地理解监听者模式,并在实际项目中应用它。
如果你有任何问题或建议,欢迎在评论区留言!