48. 设计模式综合案例3:设计一个网络框架(观察者+代理+工厂)

说实话,面试问到设计模式综合案例,很多人就慌了。单个模式背得滚瓜烂熟,一组合就乱套。今天咱们就拿一个真实的网络框架来拆解,看看观察者、代理、工厂这三个模式怎么配合,才能写出既灵活又稳定的代码。

为什么需要三个模式一起上?

网络框架的核心痛点是什么?我总结了两点:

  • 连接管理复杂:客户端连上来、断开、重连,各种状态变化要通知到业务层
  • 对象创建繁琐:每个连接对应一个会话对象,不同协议要创建不同的处理器

你想想看,如果所有逻辑都揉在一个类里,那代码基本就废了。改一个地方,崩一片。所以我们需要把职责拆开——观察者模式管通知,代理模式管访问控制,工厂模式管创建。各司其职,互不干扰。

核心思路:用工厂创建连接对象,用代理包装连接的安全校验和日志,用观察者把连接状态变化推送给所有订阅者。

先画一张架构图

下面这张图展示了三个模式如何协作。我习惯先画图再写代码,这样心里有谱。

工厂模式 ConnectionFactory 创建 TcpConnection 创建 HttpConnection 代理模式 ConnectionProxy 权限校验 日志记录 观察者模式 ConnectionSubject 通知状态变化 广播给所有观察者 创建 转发 观察者列表(Observer) LogObserver | StatsObserver | BusinessObserver 通知 整体流程: 1. 工厂创建连接对象 → 2. 代理包装连接 → 3. 观察者监听状态变化 客户端只与代理交互,无需关心底层实现

第一步:工厂模式——统一创建入口

网络框架里,连接类型可能有很多种。TCP、UDP、WebSocket……如果业务代码里到处 new,后期加一种协议就得改一堆地方。工厂模式就是干这个的——把创建逻辑收拢到一个地方。

// 抽象产品
class Connection {
public:
    virtual ~Connection() = default;
    virtual void connect(const std::string& addr) = 0;
    virtual void send(const std::string& data) = 0;
    virtual void close() = 0;
};

// 具体产品
class TcpConnection : public Connection {
public:
    void connect(const std::string& addr) override {
        std::cout << "[TCP] 连接到 " << addr << std::endl;
    }
    void send(const std::string& data) override {
        std::cout << "[TCP] 发送: " << data << std::endl;
    }
    void close() override {
        std::cout << "[TCP] 关闭连接" << std::endl;
    }
};

// 工厂
class ConnectionFactory {
public:
    static std::unique_ptr<Connection> create(const std::string& type) {
        if (type == "tcp") return std::make_unique<TcpConnection>();
        if (type == "http") return std::make_unique<HttpConnection>();
        // 可以继续扩展
        return nullptr;
    }
};

我的习惯:工厂方法里不要写死类型列表。可以用注册表模式,让每种连接自己注册到工厂。这样加新协议不用改工厂代码,符合开闭原则。

第二步:代理模式——加一层安全屏障

连接创建好了,能直接给业务层用吗?不行。你想想看,如果业务层直接操作连接对象,那权限校验、日志记录、流量统计这些横切关注点怎么办?每个业务代码里都写一遍?太丑了。

代理模式就是在这里插一脚。它和真实连接实现同一个接口,但内部包了一层额外的逻辑。

class ConnectionProxy : public Connection {
public:
    ConnectionProxy(std::unique_ptr<Connection> real, const std::string& user)
        : real_(std::move(real)), user_(user) {}

    void connect(const std::string& addr) override {
        if (!checkPermission()) {
            throw std::runtime_error("权限不足");
        }
        log("connect", addr);
        real_->connect(addr);
    }

    void send(const std::string& data) override {
        log("send", data);
        real_->send(data);
    }

    void close() override {
        log("close", "");
        real_->close();
    }

private:
    bool checkPermission() {
        // 实际项目中查权限表
        return user_ == "admin";
    }

    void log(const std::string& action, const std::string& detail) {
        std::cout << "[Proxy] " << user_ << " " << action
                  << " - " << detail << std::endl;
    }

    std::unique_ptr<Connection> real_;
    std::string user_;
};

我曾经踩过的坑:代理类里忘记转发析构函数,导致真实连接泄漏。记得把析构函数也代理掉,或者用智能指针管理生命周期。

第三步:观察者模式——状态变化及时通知

连接状态变了,比如断开了、重连成功了,业务层需要知道。如果让业务层轮询,效率低不说,代码还耦合。观察者模式就是解决这个问题的——连接状态变化时,主动通知所有订阅者。

// 观察者接口
class ConnectionObserver {
public:
    virtual ~ConnectionObserver() = default;
    virtual void onConnected(const std::string& addr) = 0;
    virtual void onDisconnected(const std::string& reason) = 0;
    virtual void onError(int code, const std::string& msg) = 0;
};

// 被观察者(主题)
class ConnectionSubject {
public:
    void attach(ConnectionObserver* obs) {
        observers_.push_back(obs);
    }

    void detach(ConnectionObserver* obs) {
        observers_.erase(
            std::remove(observers_.begin(), observers_.end(), obs),
            observers_.end()
        );
    }

protected:
    void notifyConnected(const std::string& addr) {
        for (auto* obs : observers_) {
            obs->onConnected(addr);
        }
    }

    void notifyDisconnected(const std::string& reason) {
        for (auto* obs : observers_) {
            obs->onDisconnected(reason);
        }
    }

private:
    std::vector<ConnectionObserver*> observers_;
};

// 具体观察者
class LogObserver : public ConnectionObserver {
public:
    void onConnected(const std::string& addr) override {
        std::cout << "[Log] 连接成功: " << addr << std::endl;
    }
    void onDisconnected(const std::string& reason) override {
        std::cout << "[Log] 断开连接: " << reason << std::endl;
    }
    void onError(int code, const std::string& msg) override {
        std::cout << "[Log] 错误 " << code << ": " << msg << std::endl;
    }
};

三个模式如何配合?

光有代码还不够,得看它们怎么串起来。我画个时序图你就明白了:

  1. 工厂创建:客户端调用 ConnectionFactory::create("tcp"),得到一个裸连接
  2. 代理包装:把裸连接传给 ConnectionProxy,同时传入用户身份
  3. 观察者注册:业务层创建 LogObserver,注册到代理对象上(代理内部持有主题)
  4. 正常使用:客户端调用代理的 connect(),代理先校验权限,再转发给真实连接,同时通知观察者
// 使用示例
int main() {
    // 1. 工厂创建
    auto raw = ConnectionFactory::create("tcp");

    // 2. 代理包装
    ConnectionProxy proxy(std::move(raw), "admin");

    // 3. 注册观察者
    LogObserver logger;
    proxy.attach(&logger);  // 代理继承自 ConnectionSubject

    // 4. 使用
    proxy.connect("192.168.1.1:8080");
    proxy.send("Hello");
    proxy.close();

    return 0;
}

避坑指南

这个组合方案我用了好几年,有几个地方特别容易翻车:

问题 表现 解决方案
观察者泄漏 观察者对象销毁后,主题还持有指针 用 weak_ptr 管理观察者,或者在析构时自动 detach
代理转发遗漏 真实连接新增了方法,代理没跟上 用接口隔离原则,代理和真实连接共用一个纯虚接口
工厂扩展困难 加一种协议就要改工厂代码 用注册表+模板,让每种连接自己注册

总结一下:工厂管生,代理管养,观察者管死。三个模式各管一摊,互不越界。代码写出来清晰,后期维护也省心。面试官问到这个组合,你把这套逻辑讲清楚,基本就稳了。

专注资料整理