48. 设计模式综合案例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;
}
};
三个模式如何配合?
光有代码还不够,得看它们怎么串起来。我画个时序图你就明白了:
- 工厂创建:客户端调用
ConnectionFactory::create("tcp"),得到一个裸连接 - 代理包装:把裸连接传给
ConnectionProxy,同时传入用户身份 - 观察者注册:业务层创建
LogObserver,注册到代理对象上(代理内部持有主题) - 正常使用:客户端调用代理的
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 |
| 代理转发遗漏 | 真实连接新增了方法,代理没跟上 | 用接口隔离原则,代理和真实连接共用一个纯虚接口 |
| 工厂扩展困难 | 加一种协议就要改工厂代码 | 用注册表+模板,让每种连接自己注册 |
总结一下:工厂管生,代理管养,观察者管死。三个模式各管一摊,互不越界。代码写出来清晰,后期维护也省心。面试官问到这个组合,你把这套逻辑讲清楚,基本就稳了。