设计模式实战:工厂模式、策略模式、观察者模式在大型项目中的应用
说实话,设计模式这东西,很多新手觉得是花架子。我早年也这么想,直到在一个千万行代码的项目里被坑得体无完肤。那时候我才明白——设计模式不是用来炫技的,是用来救命的。
今天咱们就聊三个最常用的模式:工厂、策略、观察者。我会结合真实项目场景,告诉你它们到底怎么用,以及什么时候该用、什么时候千万别用。
一、工厂模式:把对象的创建权收回来
工厂模式的核心思想很简单:别让调用方自己去 new 对象。你想想看,如果代码里到处都是 new XXX(),一旦构造函数变了,你得改多少个地方?
1.1 简单工厂 vs 抽象工厂
我个人习惯把工厂模式分成两类:
- 简单工厂:一个函数,根据参数返回不同对象。适合产品种类少、变化不频繁的场景。
- 抽象工厂:一组工厂接口,每个工厂生产一族产品。适合产品族概念明确的系统。
我在项目中遇到过最典型的场景是消息队列的封装。当时系统要支持 Kafka、RabbitMQ、RocketMQ 三种消息中间件。如果每个业务模块都直接去 new 一个 Producer,那切换中间件就是一场噩梦。
核心原则:工厂模式把「创建」和「使用」解耦。调用方只关心接口,不关心具体实现。
1.2 代码示例:消息队列工厂
// 抽象产品
class IMessageProducer {
public:
virtual ~IMessageProducer() = default;
virtual bool Send(const std::string& topic, const std::string& msg) = 0;
};
// 具体产品
class KafkaProducer : public IMessageProducer {
public:
bool Send(const std::string& topic, const std::string& msg) override {
// Kafka 发送逻辑
return true;
}
};
class RabbitMQProducer : public IMessageProducer {
public:
bool Send(const std::string& topic, const std::string& msg) override {
// RabbitMQ 发送逻辑
return true;
}
};
// 工厂
class MessageProducerFactory {
public:
static std::unique_ptr<IMessageProducer> Create(const std::string& type) {
if (type == "kafka") return std::make_unique<KafkaProducer>();
if (type == "rabbitmq") return std::make_unique<RabbitMQProducer>();
throw std::invalid_argument("Unknown type: " + type);
}
};
避坑指南:我曾经在工厂里塞了太多 if-else,导致每次新增产品都要改工厂代码。后来改用注册表模式,把产品类注册到工厂里,才解决了开闭原则的问题。
二、策略模式:把算法抽出来,让它们互相替换
策略模式说白了就是:把「做什么」和「怎么做」分开。你定义好接口,然后不同的策略去实现它。调用方只需要知道接口,不需要关心具体算法。
2.1 什么时候用策略模式?
我总结了一个判断标准:当你发现代码里有一堆 if-else 或者 switch-case,而且每个分支做的事情完全不同时,就该考虑策略模式了。
举个例子,一个支付系统。微信支付、支付宝、银联,它们的签名算法、回调处理、退款流程都不一样。如果你把这些逻辑全塞到一个函数里,那这个函数迟早会变成一团乱麻。
2.2 代码示例:支付策略
// 策略接口
class IPaymentStrategy {
public:
virtual ~IPaymentStrategy() = default;
virtual bool Pay(double amount) = 0;
virtual bool Refund(const std::string& orderId) = 0;
};
// 具体策略
class WeChatPay : public IPaymentStrategy {
public:
bool Pay(double amount) override {
// 微信支付逻辑
return true;
}
bool Refund(const std::string& orderId) override {
// 微信退款逻辑
return true;
}
};
class Alipay : public IPaymentStrategy {
public:
bool Pay(double amount) override {
// 支付宝支付逻辑
return true;
}
bool Refund(const std::string& orderId) override {
// 支付宝退款逻辑
return true;
}
};
// 上下文
class PaymentContext {
private:
std::unique_ptr<IPaymentStrategy> strategy_;
public:
void SetStrategy(std::unique_ptr<IPaymentStrategy> strategy) {
strategy_ = std::move(strategy);
}
bool ExecutePayment(double amount) {
return strategy_->Pay(amount);
}
};
注意:策略模式不是万能的。如果策略之间有很多共享状态,或者策略数量极少且几乎不变,那用 if-else 反而更清晰。别为了用模式而用模式。
三、观察者模式:一对多的依赖关系,怎么解耦?
观察者模式,也叫发布-订阅模式。它的核心是:当一个对象状态发生变化时,所有依赖它的对象都能自动收到通知。
我在项目中用过最典型的地方是 UI 事件系统。用户点击按钮,多个模块需要响应——日志模块要记录,统计模块要上报,业务模块要处理。如果把这些逻辑硬编码到按钮的点击事件里,那代码就彻底耦合死了。
3.1 观察者模式的结构
- Subject(主题):维护观察者列表,提供注册、注销、通知接口。
- Observer(观察者):定义更新接口,主题状态变化时被调用。
- ConcreteSubject(具体主题):存储状态,状态变化时通知所有观察者。
- ConcreteObserver(具体观察者):实现更新接口,处理通知。
3.2 代码示例:事件通知系统
// 观察者接口
class IObserver {
public:
virtual ~IObserver() = default;
virtual void OnEvent(const std::string& eventName, void* data) = 0;
};
// 主题
class EventBus {
private:
std::unordered_map<std::string, std::vector<IObserver*>> observers_;
public:
void Register(const std::string& eventName, IObserver* observer) {
observers_[eventName].push_back(observer);
}
void Unregister(const std::string& eventName, IObserver* observer) {
auto& vec = observers_[eventName];
vec.erase(std::remove(vec.begin(), vec.end(), observer), vec.end());
}
void Notify(const std::string& eventName, void* data) {
for (auto* obs : observers_[eventName]) {
obs->OnEvent(eventName, data);
}
}
};
// 具体观察者
class Logger : public IObserver {
public:
void OnEvent(const std::string& eventName, void* data) override {
std::cout << "Log: " << eventName << std::endl;
}
};
class Analytics : public IObserver {
public:
void OnEvent(const std::string& eventName, void* data) override {
std::cout << "Analytics: " << eventName << std::endl;
}
};
避坑指南:我曾经在观察者模式里踩过一个坑——观察者回调中又触发了主题的状态变化,导致无限递归。后来我加了一个递归深度限制,超过一定层数直接抛异常。嗯,这招虽然粗暴,但有效。
四、三种模式的对比与选择
| 模式 | 核心目的 | 适用场景 | 常见误区 |
|---|---|---|---|
| 工厂模式 | 封装对象创建 | 对象创建逻辑复杂、需要统一管理 | 过度抽象,简单场景也硬套工厂 |
| 策略模式 | 封装算法/行为 | 多个算法可互换、消除条件分支 | 策略数量少时反而增加复杂度 |
| 观察者模式 | 一对多通知 | 状态变化需要通知多个模块 | 忘记注销观察者导致内存泄漏 |
五、知识体系结构图
六、实战建议:先写烂代码,再重构出模式
我见过太多人一上来就套设计模式,结果代码变得又臭又长。其实正确的做法是:先写一个能跑的版本,哪怕全是 if-else 也没关系。等代码稳定了,你自然能看出哪些地方需要抽象。
举个例子,我参与过一个交易系统。一开始支付逻辑全写在一个函数里,后来要接入新的支付方式,每次都要改那个函数。改到第三次,我实在受不了了,才抽出策略模式。嗯,重构完以后,新增支付方式只需要加一个新类,再注册一下就行,原来的代码一行都不用动。
记住:设计模式是重构的结果,不是设计的前提。先让代码跑起来,再让它变好。
好了,今天的内容就到这里。工厂模式帮你管好对象的创建,策略模式帮你管好算法的替换,观察者模式帮你管好模块间的通知。这三个模式用好了,你的代码会清爽很多。