C++ 设计模式实战:RAII、观察者、工厂

用现代 C++(C++17/20)实现三种高频设计模式:RAII 资源管理、观察者模式事件系统、工厂模式插件架构。每种模式给出问题场景、实现代码和真实工程案例。

设计模式不是装饰——是经过验证的问题解决方案

这篇文章聚焦三种在 C++ 工程中最常见、最容易被误用的模式:RAII、观察者、工厂。每种模式都从一个具体的工程痛点出发,给出现代 C++ 的实现,并结合真实场景说明为什么这么做。


RAII:资源获取即初始化

问题场景

你在写 HID 设备管理,需要打开设备句柄,做一些操作,最后关闭。最初的写法大概是这样:

// ❌ 危险写法:多个出口,每个都要记得 CloseHandle
HANDLE hDevice = CreateFile(devicePath, GENERIC_READ | GENERIC_WRITE, ...);
if (hDevice == INVALID_HANDLE_VALUE) return false;

if (!HidD_GetAttributes(hDevice, &attributes)) {
    CloseHandle(hDevice);  // 记得关
    return false;
}

// 如果这里抛异常呢?句柄泄漏!
processDevice(hDevice);

CloseHandle(hDevice);  // 正常路径才会执行到
return true;

函数有 3 个出口,每个都要手写 CloseHandle。一旦中间抛了异常,或者新同事加了一个 early return,句柄就泄漏了。

RAII 包装器实现

解决方案是把资源的生命周期绑定到对象的生命周期——对象析构时资源自动释放:

#include <windows.h>
#include <functional>
#include <stdexcept>

// 通用 RAII 包装器:接受任意清理函数
class ScopeGuard {
public:
    explicit ScopeGuard(std::function<void()> cleanup)
        : cleanup_(std::move(cleanup)), active_(true) {}

    ~ScopeGuard() {
        if (active_) cleanup_();
    }

    // 提前解除(不再清理,所有权转移时使用)
    void dismiss() { active_ = false; }

    // 禁止拷贝
    ScopeGuard(const ScopeGuard&) = delete;
    ScopeGuard& operator=(const ScopeGuard&) = delete;

private:
    std::function<void()> cleanup_;
    bool active_;
};

// 专用 HID 设备句柄包装器
class HidDeviceHandle {
public:
    explicit HidDeviceHandle(const wchar_t* devicePath) {
        handle_ = CreateFileW(
            devicePath,
            GENERIC_READ | GENERIC_WRITE,
            FILE_SHARE_READ | FILE_SHARE_WRITE,
            nullptr,
            OPEN_EXISTING,
            FILE_FLAG_OVERLAPPED,
            nullptr
        );
        if (handle_ == INVALID_HANDLE_VALUE) {
            throw std::runtime_error("Failed to open HID device");
        }
    }

    ~HidDeviceHandle() {
        if (handle_ != INVALID_HANDLE_VALUE) {
            CloseHandle(handle_);
        }
    }

    // 移动语义:所有权转移
    HidDeviceHandle(HidDeviceHandle&& other) noexcept
        : handle_(other.handle_) {
        other.handle_ = INVALID_HANDLE_VALUE;
    }

    HidDeviceHandle& operator=(HidDeviceHandle&& other) noexcept {
        if (this != &other) {
            if (handle_ != INVALID_HANDLE_VALUE) CloseHandle(handle_);
            handle_ = other.handle_;
            other.handle_ = INVALID_HANDLE_VALUE;
        }
        return *this;
    }

    // 禁止拷贝(句柄不能共享所有权)
    HidDeviceHandle(const HidDeviceHandle&) = delete;
    HidDeviceHandle& operator=(const HidDeviceHandle&) = delete;

    HANDLE get() const { return handle_; }
    bool valid() const { return handle_ != INVALID_HANDLE_VALUE; }

private:
    HANDLE handle_ = INVALID_HANDLE_VALUE;
};

裸指针 vs unique_ptr vs 自定义 RAII

// ❌ 裸指针:手动管理,容易泄漏
HANDLE h = OpenDevice();
// ... 中间任何异常或 return 都会泄漏
CloseHandle(h);

// ✅ unique_ptr + 自定义 deleter:适合有 delete-like 清理函数的资源
auto deleter = [](HANDLE* p) { if (*p != INVALID_HANDLE_VALUE) CloseHandle(*p); };
std::unique_ptr<HANDLE, decltype(deleter)> hGuard(&h, deleter);

// ✅ 自定义 RAII 类:推荐方案,语义最清晰
{
    HidDeviceHandle device(L"\\\\.\\HID#...#...#{...}");
    // 任意操作,任意路径退出,句柄都会被 ~HidDeviceHandle() 关闭
    HIDD_ATTRIBUTES attrs;
    HidD_GetAttributes(device.get(), &attrs);
}  // 这里自动 CloseHandle

工程案例:多资源同时管理

bool initializeDevice(const wchar_t* path) {
    try {
        HidDeviceHandle device(path);

        // 互斥锁也可以 RAII:std::lock_guard
        std::mutex mtx;
        std::lock_guard<std::mutex> lock(mtx);

        // 临时缓冲区
        auto buffer = std::make_unique<uint8_t[]>(1024);

        // 所有资源在 try 块结束时自动释放
        // 无需任何手动清理代码
        return HidD_SetFeature(device.get(), buffer.get(), 1024);

    } catch (const std::exception& e) {
        // device 析构已经关闭句柄,这里只需处理错误逻辑
        logError(e.what());
        return false;
    }
}

RAII 的本质:用 C++ 对象的确定性析构(deterministic destruction)替代手动资源管理。任何需要”成对操作”(open/close、lock/unlock、malloc/free)的资源,都适合用 RAII 包装。


观察者模式:类型安全事件系统

问题场景

设备管理器需要在设备插拔时通知多个组件:UI 要刷新列表,日志模块要记录,业务层要更新状态。最简单(也最糟糕)的做法是在设备管理器里直接调用这些组件:

// ❌ 紧耦合:设备管理器知道太多东西
class DeviceManager {
    void onDeviceArrival() {
        ui_.refreshList();      // 依赖 UI
        logger_.logEvent(...);  // 依赖 Logger
        bizLayer_.update(...);  // 依赖业务层
    }
};

添加新订阅者需要修改 DeviceManager,违反开闭原则,而且单元测试极其困难。

基于 std::function 的类型安全事件系统

#include <functional>
#include <unordered_map>
#include <vector>
#include <string>
#include <typeindex>
#include <any>

// 事件基类(用于类型擦除)
struct EventBase {
    virtual ~EventBase() = default;
};

// 具体事件类型
struct DeviceArrivalEvent : EventBase {
    std::string devicePath;
    uint16_t vendorId;
    uint16_t productId;
};

struct DeviceRemovalEvent : EventBase {
    std::string devicePath;
};

struct DeviceDataEvent : EventBase {
    std::string devicePath;
    std::vector<uint8_t> data;
};

// 类型安全事件总线
class EventBus {
public:
    // 订阅:返回 token,用于取消订阅
    template<typename EventT>
    int subscribe(std::function<void(const EventT&)> handler) {
        auto key = std::type_index(typeid(EventT));
        int token = nextToken_++;
        handlers_[key].push_back({
            token,
            [handler](const EventBase& e) {
                handler(static_cast<const EventT&>(e));
            }
        });
        return token;
    }

    // 取消订阅
    template<typename EventT>
    void unsubscribe(int token) {
        auto key = std::type_index(typeid(EventT));
        auto it = handlers_.find(key);
        if (it == handlers_.end()) return;
        auto& vec = it->second;
        vec.erase(
            std::remove_if(vec.begin(), vec.end(),
                [token](const auto& h) { return h.token == token; }),
            vec.end()
        );
    }

    // 发布事件
    template<typename EventT>
    void publish(const EventT& event) {
        auto key = std::type_index(typeid(EventT));
        auto it = handlers_.find(key);
        if (it == handlers_.end()) return;
        // 拷贝一份,防止回调中修改 handlers_
        auto handlers = it->second;
        for (const auto& h : handlers) {
            h.fn(event);
        }
    }

private:
    struct HandlerEntry {
        int token;
        std::function<void(const EventBase&)> fn;
    };
    std::unordered_map<std::type_index, std::vector<HandlerEntry>> handlers_;
    int nextToken_ = 0;
};

// 全局事件总线(实际项目中可用依赖注入)
inline EventBus& getEventBus() {
    static EventBus bus;
    return bus;
}

使用:设备插拔事件广播

// 设备管理器:只负责发布事件,不知道谁在监听
class DeviceManager {
public:
    void onHotplugArrival(const std::string& path, uint16_t vid, uint16_t pid) {
        // 发布事件,其他组件自行处理
        getEventBus().publish(DeviceArrivalEvent{path, vid, pid});
    }

    void onHotplugRemoval(const std::string& path) {
        getEventBus().publish(DeviceRemovalEvent{path});
    }
};

// UI 组件:订阅感兴趣的事件
class DeviceListView {
public:
    DeviceListView() {
        arrivalToken_ = getEventBus().subscribe<DeviceArrivalEvent>(
            [this](const DeviceArrivalEvent& e) {
                addDeviceItem(e.devicePath, e.vendorId, e.productId);
            }
        );
        removalToken_ = getEventBus().subscribe<DeviceRemovalEvent>(
            [this](const DeviceRemovalEvent& e) {
                removeDeviceItem(e.devicePath);
            }
        );
    }

    ~DeviceListView() {
        // 析构时取消订阅,防止悬空回调
        getEventBus().unsubscribe<DeviceArrivalEvent>(arrivalToken_);
        getEventBus().unsubscribe<DeviceRemovalEvent>(removalToken_);
    }

private:
    int arrivalToken_;
    int removalToken_;
    void addDeviceItem(const std::string&, uint16_t, uint16_t) { /* ... */ }
    void removeDeviceItem(const std::string&) { /* ... */ }
};

// 日志模块:独立订阅,DeviceManager 完全不知道它的存在
class DeviceLogger {
public:
    DeviceLogger() {
        token_ = getEventBus().subscribe<DeviceArrivalEvent>(
            [](const DeviceArrivalEvent& e) {
                printf("[LOG] Device arrived: %s (VID=%04X PID=%04X)\n",
                    e.devicePath.c_str(), e.vendorId, e.productId);
            }
        );
    }
    ~DeviceLogger() {
        getEventBus().unsubscribe<DeviceArrivalEvent>(token_);
    }
private:
    int token_;
};

与 Qt Signal/Slot 对比

特性Qt Signal/Slot本文事件总线
类型安全✅(编译期)✅(模板)
跨线程✅(内置队列连接)❌(需额外处理)
依赖Qt MOC 预处理器纯标准库
运行时开销中等(元对象系统)低(std::function)
动态订阅/取消
适用场景Qt 应用无 Qt 依赖的 C++ 项目

观察者模式的核心价值:发布者和订阅者互不依赖,可以独立演化、独立测试。增加新的监听者无需修改任何现有代码。


工厂模式:注册表式插件架构

问题场景

你的应用需要处理多种协议(HID、USB Bulk、Serial、BLE),每种协议对应不同的处理器类。最直观的写法是 if-else:

// ❌ 每次新增协议都要修改这里
std::unique_ptr<ProtocolHandler> createHandler(const std::string& type) {
    if (type == "hid")    return std::make_unique<HidHandler>();
    if (type == "serial") return std::make_unique<SerialHandler>();
    if (type == "ble")    return std::make_unique<BleHandler>();
    return nullptr;
}

随着协议类型增多,这个函数会无限膨胀,而且每次改动都要触碰核心代码。

注册表式工厂(自注册)

让每个处理器类自己注册到工厂,工厂完全不知道具体类型:

#include <memory>
#include <string>
#include <unordered_map>
#include <functional>
#include <stdexcept>

// 抽象基类
class ProtocolHandler {
public:
    virtual ~ProtocolHandler() = default;
    virtual bool open(const std::string& path) = 0;
    virtual void close() = 0;
    virtual std::vector<uint8_t> read() = 0;
    virtual bool write(const std::vector<uint8_t>& data) = 0;
    virtual std::string type() const = 0;
};

// 工厂注册表
class HandlerFactory {
public:
    using Creator = std::function<std::unique_ptr<ProtocolHandler>()>;

    static HandlerFactory& instance() {
        static HandlerFactory factory;
        return factory;
    }

    // 注册创建函数
    void registerHandler(const std::string& type, Creator creator) {
        registry_[type] = std::move(creator);
    }

    // 创建实例
    std::unique_ptr<ProtocolHandler> create(const std::string& type) const {
        auto it = registry_.find(type);
        if (it == registry_.end()) {
            throw std::runtime_error("Unknown handler type: " + type);
        }
        return it->second();
    }

    // 查询已注册类型
    std::vector<std::string> registeredTypes() const {
        std::vector<std::string> types;
        for (const auto& [key, _] : registry_) types.push_back(key);
        return types;
    }

private:
    std::unordered_map<std::string, Creator> registry_;
};

// 自注册辅助类模板
template<typename T>
class AutoRegister {
public:
    explicit AutoRegister(const std::string& type) {
        HandlerFactory::instance().registerHandler(
            type,
            []() -> std::unique_ptr<ProtocolHandler> {
                return std::make_unique<T>();
            }
        );
    }
};

// 自注册宏(简化样板代码)
#define REGISTER_HANDLER(ClassName, TypeName) \
    static AutoRegister<ClassName> _autoReg_##ClassName{TypeName}

具体处理器实现(自注册)

// hid_handler.cpp
class HidHandler : public ProtocolHandler {
public:
    bool open(const std::string& path) override {
        handle_ = CreateFileA(path.c_str(), GENERIC_READ | GENERIC_WRITE,
                              FILE_SHARE_READ | FILE_SHARE_WRITE,
                              nullptr, OPEN_EXISTING, 0, nullptr);
        return handle_ != INVALID_HANDLE_VALUE;
    }

    void close() override {
        if (handle_ != INVALID_HANDLE_VALUE) {
            CloseHandle(handle_);
            handle_ = INVALID_HANDLE_VALUE;
        }
    }

    std::vector<uint8_t> read() override {
        std::vector<uint8_t> buf(65);
        DWORD bytesRead = 0;
        ReadFile(handle_, buf.data(), 65, &bytesRead, nullptr);
        buf.resize(bytesRead);
        return buf;
    }

    bool write(const std::vector<uint8_t>& data) override {
        DWORD written = 0;
        return WriteFile(handle_, data.data(), data.size(), &written, nullptr);
    }

    std::string type() const override { return "hid"; }

private:
    HANDLE handle_ = INVALID_HANDLE_VALUE;
};

// 在 .cpp 文件全局作用域自动注册(程序启动时执行)
REGISTER_HANDLER(HidHandler, "hid");

// serial_handler.cpp(同理,自包含,不修改工厂)
class SerialHandler : public ProtocolHandler {
    // ... 实现
    std::string type() const override { return "serial"; }
};
REGISTER_HANDLER(SerialHandler, "serial");

使用工厂

// 调用方完全不依赖具体类型
void connectToDevice(const std::string& type, const std::string& path) {
    auto handler = HandlerFactory::instance().create(type);
    if (!handler->open(path)) {
        throw std::runtime_error("Failed to open device");
    }
    // handler 是 unique_ptr,RAII 保证 close
}

// 动态加载配置(从 JSON/命令行读取类型字符串)
void loadDevicesFromConfig(const std::vector<DeviceConfig>& configs) {
    for (const auto& cfg : configs) {
        try {
            auto handler = HandlerFactory::instance().create(cfg.type);
            handler->open(cfg.path);
            // ...
        } catch (const std::exception& e) {
            fprintf(stderr, "Failed to create handler for %s: %s\n",
                    cfg.type.c_str(), e.what());
        }
    }
}

C++17 if constexpr 与 std::variant 在工厂中的应用

工厂模式结合 std::variant 可以在编译期消除运行时分支,适合类型集合固定的场景:

#include <variant>

// 已知类型集合(编译期固定)
using HandlerVariant = std::variant<HidHandler, SerialHandler, BleHandler>;

// 根据枚举创建变体(编译期分派)
enum class HandlerType { Hid, Serial, Ble };

HandlerVariant createHandlerVariant(HandlerType type) {
    switch (type) {
        case HandlerType::Hid:    return HidHandler{};
        case HandlerType::Serial: return SerialHandler{};
        case HandlerType::Ble:    return BleHandler{};
    }
    std::unreachable();  // C++23,C++17 用 __builtin_unreachable()
}

// std::visit + if constexpr:针对不同类型执行不同逻辑
void processHandler(HandlerVariant& handler) {
    std::visit([](auto& h) {
        using T = std::decay_t<decltype(h)>;

        if constexpr (std::is_same_v<T, HidHandler>) {
            // HID 特有操作:设置报告描述符
            h.setReportDescriptor();
        } else if constexpr (std::is_same_v<T, BleHandler>) {
            // BLE 特有操作:配对
            h.startPairing();
        } else {
            // 通用操作
            h.open("/dev/ttyUSB0");
        }
    }, handler);
}

variant 工厂 vs 注册表工厂的选择

场景推荐方案
类型集合固定,编译期已知std::variant + if constexpr
类型集合动态扩展(插件系统)注册表工厂
需要运行时字符串映射(配置文件)注册表工厂
性能极敏感,无虚函数开销std::variant

三种模式的协同

这三种模式在实际项目中往往配合使用:

工厂模式
  └─ 创建具体的 ProtocolHandler(RAII 包装底层句柄)
       └─ 发布事件到 EventBus(观察者)
            ├─ UI 订阅 → 刷新设备列表
            ├─ Logger 订阅 → 记录日志
            └─ Monitor 订阅 → 更新状态机
  • RAII 确保资源不泄漏,不论执行路径如何
  • 观察者 解耦生产者和消费者,两侧可独立变化
  • 工厂 隔离创建逻辑,调用方不依赖具体类型

这不是理论,是在 HID 设备管理、协议层抽象、UI 事件系统中被反复验证过的工程实践。