设计模式实战:工厂模式、策略模式、观察者模式在大型项目中的应用

说实话,设计模式这东西,很多新手觉得是花架子。我早年也这么想,直到在一个千万行代码的项目里被坑得体无完肤。那时候我才明白——设计模式不是用来炫技的,是用来救命的

今天咱们就聊三个最常用的模式:工厂、策略、观察者。我会结合真实项目场景,告诉你它们到底怎么用,以及什么时候该用、什么时候千万别用。

一、工厂模式:把对象的创建权收回来

工厂模式的核心思想很简单:别让调用方自己去 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;
    }
};

避坑指南:我曾经在观察者模式里踩过一个坑——观察者回调中又触发了主题的状态变化,导致无限递归。后来我加了一个递归深度限制,超过一定层数直接抛异常。嗯,这招虽然粗暴,但有效。

四、三种模式的对比与选择

模式 核心目的 适用场景 常见误区
工厂模式 封装对象创建 对象创建逻辑复杂、需要统一管理 过度抽象,简单场景也硬套工厂
策略模式 封装算法/行为 多个算法可互换、消除条件分支 策略数量少时反而增加复杂度
观察者模式 一对多通知 状态变化需要通知多个模块 忘记注销观察者导致内存泄漏

五、知识体系结构图

设计模式实战:三大模式在大型项目中的应用 工厂模式 核心:封装对象创建 场景:消息队列、数据库连接 变体:简单工厂、抽象工厂 注意:避免过度抽象 策略模式 核心:封装算法/行为 场景:支付、排序、校验 关键:消除条件分支 注意:策略数量少时慎用 观察者模式 核心:一对多通知 场景:事件系统、UI回调 关键:解耦发布者和订阅者 注意:防止内存泄漏 共同目标:降低耦合、提高可维护性 选择依据:根据变化点和复杂度决定 实战建议:先写烂代码,再重构出模式 不要为了用模式而用模式,模式是工具,不是目的

六、实战建议:先写烂代码,再重构出模式

我见过太多人一上来就套设计模式,结果代码变得又臭又长。其实正确的做法是:先写一个能跑的版本,哪怕全是 if-else 也没关系。等代码稳定了,你自然能看出哪些地方需要抽象。

举个例子,我参与过一个交易系统。一开始支付逻辑全写在一个函数里,后来要接入新的支付方式,每次都要改那个函数。改到第三次,我实在受不了了,才抽出策略模式。嗯,重构完以后,新增支付方式只需要加一个新类,再注册一下就行,原来的代码一行都不用动。

记住:设计模式是重构的结果,不是设计的前提。先让代码跑起来,再让它变好。

好了,今天的内容就到这里。工厂模式帮你管好对象的创建,策略模式帮你管好算法的替换,观察者模式帮你管好模块间的通知。这三个模式用好了,你的代码会清爽很多。

公众号:蓝海资料掘金营,微信deep3321