1.单例模式(Singleton):全局唯一资源的管理者
实例场景:
每个线程一个 EventLoop,主线程要能查到"thread X 的 loop 是哪个";全局配置对象、全局 logger、连接 ID 生成器。
没它会怎样:
把 EventLoop 指针传来传去,每个函数签名都多一个参数;或者滥用全局变量,初始化顺序一团乱(跨编译单元的全局对象初始化顺序是未定义)。
实现(C++ 的现代写法):
c
class EventLoopRegistry {
public:
static EventLoopRegistry& instance() {
static EventLoopRegistry inst; // Meyers 单例:C++11 起局部静态变量
return inst; // 初始化是线程安全的(magic statics)
}
EventLoop* find(pid_t tid) { /* 查表 */ }
private:
EventLoopRegistry() = default; // 私有化构造
~EventLoopRegistry() = default;
EventLoopRegistry(const EventLoopRegistry&) = delete;
EventLoopRegistry& operator=(const EventLoopRegistry&) = delete;
std::unordered_map<pid_t, EventLoop*> loops_;
};
工程要点:
- 别用双检锁(DCLP)那套老写法了,magic statics 语义就是正确的,并发环境多个线程同时调用EventLoopRegistry::instance()编译器会保证有序访问,只产生一个全局实例。
- 单例的真正问题不是怎么实现,而是该不该用。依据:是否真的是"全局唯一资源"(线程表是,普通业务对象不是);
2.工厂模式(Factory):屏蔽多态对象的创建细节
实际场景:
Poller 多后端------Linux 5.10+ 用 io_uring,老内核用 epoll,mac 开发机用 kqueue,测试环境用 PollPoller。调用方(EventLoop)只想说"给我一个 Poller",不该知道 new 哪个子类。
没它会怎样:
EventLoop 里塞满 #ifdef linux 和版本判断,new EpollPoller / new UringPoller 散落各处,加一个新后端要改 N 个文件。
实现(简单工厂 + 注册表,这是 STL 风格的现代写法):
c
// 一、被创建对象的抽象基类:Poller
class EventLoop; // 前向声明,本例中仅占位
class Poller {
};
// 二、注册表:名字 -> 构造函数(工厂函数)
class PollerFactoryRegistry {
public:
// 工厂函数签名:只依赖 EventLoop*,返回独占所有权
using FactoryFn = std::function<std::unique_ptr<Poller>(EventLoop*)>;
// magic statics(见"单例"一节):线程安全、惰性初始化
static PollerFactoryRegistry& instance() {
static PollerFactoryRegistry r;
return r;
}
// 注册发生在 main 之前的静态初始化阶段,天然单线程,无需加锁
void add(const std::string& name, FactoryFn fn) {
if (!factories_.emplace(name, std::move(fn)).second) {
std::fprintf(stderr, "PollerFactory: duplicate registration '%s'\n", name.c_str());
std::abort(); // 重复注册是编码错误,直接暴露
}
}
// 按名创建 ------ 名字不对是配置错误,抛异常让启动失败(fail fast)
std::unique_ptr<Poller> create(const std::string& name, EventLoop* loop) const {
auto it = factories_.find(name);
if (it == factories_.end()) {
,,,
}
return it->second(loop);
}
private:
std::map<std::string, FactoryFn> factories_;
};
struct PollerRegistrar {
PollerRegistrar(const char* name, PollerFactoryRegistry::FactoryFn fn) {
PollerFactoryRegistry::instance().add(name, std::move(fn));
}
};
// 三、具体后端 1:EpollPoller(模拟独立编译单元 epoll_poller.cc)
class EpollPoller : public Poller {
};
namespace {
PollerRegistrar _reg_epoll("epoll", [](EventLoop* l) {
return std::make_unique<EpollPoller>(l);
});
} // namespace
// 四、具体后端 2:PollPoller(模拟 poll_poller.cc)
class PollPoller : public Poller {
};
namespace {
PollerRegistrar _reg_poll("poll", [](EventLoop* l) {
return std::make_unique<PollPoller>(l);
});
} // namespace
// 六、main:调用方完全不知道具体后端的存在
int main() {
EventLoop loop;
// 工厂入口
std::unique_ptr<Poller> poller = PollerFactoryRegistry::instance().create("poll", &loop);
return 0;
}
工程要点:
- 先注册到工厂对象;再通过工厂接口实现特定类别注册对象的获取;
- 返回值用 std::unique_ptr------所有权语义的自我声明,工厂不保留、不共享。
- 注意上述用
std::unique_ptr<Driver>移动构造出一个std::unique_ptr<Base>的用法。
3.观察者模式(Observer):一对多的事件通知
实际场景:
TCP 连接的连接建立/数据到达/断开/错误事件;配置中心推送到所有订阅者(你的分布式系统里一定有 config watch 的需求)。
没它会怎样:
一个连接一个 onMessage 回调函数,多个模块都关心怎么办?链式调用硬编码,新增订阅者改框架代码------不可接受。
实现(C++ 现代观察者 = std::function + weak_ptr):
c
class Connection {
public:
using MessageCallback = std::function<void(const ConnectionPtr&, Buffer&)>;
using CloseCallback = std::function<void(const ConnectionPtr&)>;
// 允许挂多个观察者(注意:生产代码通常只有一个 onMessage,这里是展示多观察者)
void addMessageHandler(std::weak_ptr<void> owner, MessageCallback cb) {
handlers_.push_back({std::move(owner), std::move(cb)});
}
void onMessage() {
for (auto it = handlers_.begin(); it != handlers_.end(); ) {
if (auto owner = it->owner.lock()) { // 观察者还活着吗?
it->cb(shared_from_this(), inputBuffer_);
++it;
} else {
it = handlers_.erase(it); // 自动清理死掉的观察者
}
}
}
private:
struct Handler { std::weak_ptr<void> owner; MessageCallback cb; };
std::vector<Handler> handlers_;
};
工程要点:
- weak_ptr 防悬空是观察者的头号生存法则。
4.策略模式(Strategy):运行时可替换的算法
实际场景:
负载均衡(轮询/随机/一致性哈希/最少连接);RPC 的重试策略(不重试/固定间隔/指数退避+jitter);超时策略。
没它会怎样:
一个 chooseNode() 函数里 switch-case 四种算法,每个 RPC 框架都这样写,最后长成 800 行的上帝函数。
实现(C++ 有两条路,都很有代表性):
- 路线一:运行时多态(算法数量少、需要配置切换时用)
c
class LoadBalancer {
public:
virtual ~LoadBalancer() = default;
virtual const Node& select(const std::vector<Node>& nodes, uint64_t key) = 0;
};
class RoundRobin : public LoadBalancer {
public:
const Node& select(const std::vector<Node>& nodes, uint64_t) override {
}
};
class ConsistentHash : public LoadBalancer {
public:
const Node& select(const std::vector<Node>&, uint64_t key) override {
}
};
// 配置驱动:"lb.strategy = consistent_hash" → 工厂创建
// 利用工厂取得具体的策略对象
std::unique_ptr<LoadBalancer> lb = LoadBalancerFactory::create(config);
// 通过基类指针调用虚方法完成策略使用
lb->select(...);