3. 简单工厂模式:模式动机与定义、角色与结构、C++代码实现、优缺点分析

3.1 模式动机:为什么需要简单工厂?

先聊聊一个实际场景。我记得刚入行那会儿,接手一个日志系统。项目里到处是 new FileLogger()new ConsoleLogger()new DatabaseLogger()。每次新增一种日志类型,就得满世界找那些 new 语句,改完还得重新编译。说实话,那感觉就像在玩打地鼠——改完一个,又冒出来一个。

为什么会这样?因为客户端代码直接依赖了具体类。你想想看,如果能把「创建对象」这件事集中管理,客户端只关心「我要一个日志对象」,而不关心「它到底是文件日志还是数据库日志」,是不是就清爽多了?

这就是简单工厂模式的动机——将对象的创建和使用分离。说白了,就是找个专门的「工厂」来替你 new 对象。

核心思想:用一个工厂类封装对象创建逻辑,客户端通过参数告诉工厂「我要什么」,工厂负责返回对应的产品实例。

3.2 模式定义

简单工厂模式(Simple Factory Pattern)属于创建型模式,但它并不在 GoF 的 23 种经典设计模式之列。我个人习惯把它看作一种「编程惯用法」——它更像是一个过渡方案,为后续引入工厂方法或抽象工厂做铺垫。

定义如下:定义一个工厂类,根据传入的参数动态决定创建哪一种产品类的实例。

3.3 角色与结构

这个模式涉及三个角色,不多,但每个都很关键:

角色 名称 职责
工厂(Factory) 简单工厂的核心 提供静态方法,根据参数创建具体产品
抽象产品(Product) 产品基类或接口 定义产品的公共接口
具体产品(ConcreteProduct) 产品实现类 实现抽象产品的接口,完成具体业务

结构图如下:

Factory + CreateProduct(type): Product* Product(抽象) + Operation(): void ConcreteProductA + Operation(): void ConcreteProductB + Operation(): void ConcreteProductC + Operation(): void 继承 继承 继承

3.4 C++ 代码实现

直接上代码。我们以日志系统为例:

// 抽象产品
class Logger {
public:
    virtual ~Logger() = default;
    virtual void WriteLog(const std::string& msg) = 0;
};

// 具体产品:文件日志
class FileLogger : public Logger {
public:
    void WriteLog(const std::string& msg) override {
        std::cout << "[FileLogger] " << msg << std::endl;
    }
};

// 具体产品:控制台日志
class ConsoleLogger : public Logger {
public:
    void WriteLog(const std::string& msg) override {
        std::cout << "[ConsoleLogger] " << msg << std::endl;
    }
};

// 具体产品:数据库日志
class DatabaseLogger : public Logger {
public:
    void WriteLog(const std::string& msg) override {
        std::cout << "[DatabaseLogger] " << msg << std::endl;
    }
};

// 简单工厂
class LoggerFactory {
public:
    enum LoggerType { File, Console, Database };

    static Logger* CreateLogger(LoggerType type) {
        switch (type) {
            case File:     return new FileLogger();
            case Console:  return new ConsoleLogger();
            case Database: return new DatabaseLogger();
            default:
                throw std::invalid_argument("Unknown logger type");
        }
    }
};

// 客户端使用
int main() {
    Logger* logger = LoggerFactory::CreateLogger(LoggerFactory::File);
    logger->WriteLog("系统启动成功");
    delete logger;

    logger = LoggerFactory::CreateLogger(LoggerFactory::Console);
    logger->WriteLog("用户登录");
    delete logger;

    return 0;
}
小技巧:我习惯把工厂方法声明为 static,这样客户端不需要实例化工厂就能直接调用。但要注意,静态方法不利于扩展——如果你想通过继承来改变工厂行为,静态方法就不好使了。

3.5 优缺点分析

任何模式都有两面性。简单工厂用起来顺手,但坑也不少。

优点

  • 封装创建逻辑:客户端不需要知道具体类名,只需要一个参数。说白了,把「怎么 new」这件事藏起来了。
  • 集中管理:所有对象的创建都在一个地方,修改起来方便。我曾经在一个项目中,把 20 多处散落的 new 收拢到一个工厂里,改需求时只改一个文件,爽得很。
  • 代码更清晰:客户端代码只依赖抽象接口,不依赖具体实现。这符合「依赖倒置原则」的精神。

缺点

  • 违背开闭原则:每次新增产品类型,都得修改工厂类的 switchif-else。嗯,这里要注意——如果产品类型频繁变化,简单工厂就不太合适了。
  • 工厂类过于臃肿:产品类型一多,工厂方法里全是分支判断,代码可读性直线下降。
  • 静态方法限制:无法通过继承来改变工厂行为。如果你需要「可配置的工厂」,简单工厂就力不从心了。
避坑指南:我曾经在一个项目中,用简单工厂管理 30 多种产品类型。结果每次加新产品,都要改工厂类,改完还要重新测试所有分支。后来我重构成了工厂方法模式,才彻底解脱。所以我的建议是:产品类型少于 5 种时,用简单工厂很舒服;超过 5 种,考虑工厂方法或抽象工厂。

3.6 适用场景

什么时候用简单工厂?我总结了三个典型场景:

  1. 产品类型较少且稳定:比如你的系统只支持两三种日志方式,而且短期内不会增加。
  2. 客户端只需要知道产品类型参数:比如传入字符串 "file" 或 "console",工厂帮你搞定一切。
  3. 作为重构的起点:如果你发现代码里到处都是 new,先引入简单工厂收拢创建逻辑,后续再考虑更灵活的模式。

简单工厂虽然「简单」,但它是理解工厂方法模式和抽象工厂模式的基础。我个人建议初学者先把这个模式吃透,后面两个模式学起来会轻松很多。

一句话总结:简单工厂把对象的创建集中管理,让客户端只关心「用什么」,不关心「怎么造」。它简单、实用,但别滥用——产品类型一多,就该考虑升级方案了。

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