English 简体中文 繁體中文 한국 사람 日本語 Deutsch русский بالعربية TÜRKÇE português คนไทย french
查看: 8|回复: 0

编程练习:编写一个监听者模式类

[复制链接]
查看: 8|回复: 0

编程练习:编写一个监听者模式类

[复制链接]
查看: 8|回复: 0

342

主题

0

回帖

1036

积分

金牌会员

积分
1036
zhidao

342

主题

0

回帖

1036

积分

金牌会员

积分
1036
2025-2-6 13:41:10 | 显示全部楼层 |阅读模式
监听者模式(Observer Pattern)是一种行为设计模式,它定义了对象之间的一对多依赖关系。当一个对象的状态发生变化时,所有依赖于它的对象都会收到通知并自动更新。这种模式非常适合用于事件驱动的系统,例如 GUI 框架、消息队列等。
在本文中,我们将通过编写一个简单的监听者模式类 Observable,来学习如何实现这一设计模式。
<hr>1. 监听者模式的核心概念

监听者模式通常包含以下两个核心组件:

  • Subject(主题):维护一个观察者列表,并提供注册、注销和通知观察者的接口。
  • Observer(观察者):定义一个更新接口,用于接收主题的通知。
在我们的实现中,Observable 类充当 Subject 的角色,而观察者是一个 std::function<void()> 回调函数。
<hr>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::unordered_map 替换为 std::map。
    std::unordered_map<Event, std::unordered_map<Handle, std::function<void()>>> observers_;    Handle nextHandle_;    std::mutex mutex_;};}  // namespace cise#endif // __OBSERVABLE_H___
 

<hr>3. 设计亮点

3.1 线程安全

使用 std::mutex 保护共享资源,确保在多线程环境下安全地添加、移除和通知观察者。
3.2 高性能

使用 std::unordered_map 存储观察者,避免了有序遍历的开销,提高了性能。
3.3 灵活性

支持任意可哈希的事件类型(如枚举、整数、字符串等),适用于多种场景。
3.4 异常处理

将异常直接传递给调用者,遵循“谁调用,谁处理”的原则。
<hr>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;}
 

<hr>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 安装路径)
<hr>运行示例


  • 编译
    make

  • 运行测试
    make test




  • 清理
    make clean
     


<hr>6. 总结

通过本次编程练习,我们实现了一个简单但功能强大的监听者模式类 Observable。它不仅支持多线程环境,还具有高性能和灵活性。希望这篇文章能帮助你更好地理解监听者模式,并在实际项目中应用它。
如果你有任何问题或建议,欢迎在评论区留言!
<hr>参考资料

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

342

主题

0

回帖

1036

积分

金牌会员

积分
1036

QQ|智能设备 | 粤ICP备2024353841号-1

GMT+8, 2025-3-10 14:50 , Processed in 0.900110 second(s), 27 queries .

Powered by 智能设备

©2025

|网站地图