简单工厂模式:模式动机与定义

说实话,简单工厂模式是我最早接触的设计模式之一。那时候我刚从学校出来,写代码还停留在「一个类干所有事」的阶段。直到有一次,我接手了一个支付模块——微信支付、支付宝、银联、Apple Pay……每次新增一种支付方式,就要改一大片 if-else。改得我头皮发麻。

后来我意识到,其实可以把「创建对象」这件事单独拎出来。这就是简单工厂模式的初衷。

模式动机

你想想看,客户端代码里如果到处都是 new XXX(),一旦要换实现,就得改源码。这违反了开闭原则。简单工厂模式的核心动机就是:将对象的创建和使用分离

说白了,客户端只需要告诉工厂「我要什么」,工厂帮你把对象造好。客户端不用关心这个对象是怎么 new 出来的,也不用关心它属于哪个具体类。

核心思想:用一个专门的工厂类来封装对象创建逻辑,客户端通过参数获取产品实例。

模式定义

简单工厂模式(Simple Factory Pattern)属于创建型模式,但它并不在 GoF 的 23 种设计模式之列。它更像是一种编程习惯,或者说是一个「准模式」。我个人的理解是:它是工厂方法模式的一个简化版,适用于产品种类不多且相对稳定的场景。

定义如下:

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

角色与结构

简单工厂模式的结构非常清晰,就三个角色。我在项目中画过无数次这种图,每次给新人讲都拿这个举例。

角色说明

角色 名称 职责
Factory 工厂类 核心类,负责创建产品对象。通常提供一个静态方法。
Product 抽象产品 定义产品的公共接口,所有具体产品必须实现它。
ConcreteProduct 具体产品 实现抽象产品接口的具体类,工厂负责创建它的实例。

结构图

下面这张图是我用 SVG 画的,展示了简单工厂模式的核心结构。你看,客户端只跟 Factory 和 Product 打交道,具体产品被藏起来了。

Client Factory Product(抽象) ConcreteProductA ConcreteProductB ConcreteProductC 调用工厂方法 返回产品实例 实现 实现 实现

C++ 代码实现

好了,理论说完了,咱们直接上代码。我记得有一次给团队做代码评审,看到有人用 switch-case 写了 200 行的对象创建逻辑。我当时就说:「兄弟,咱们用简单工厂重构一下吧。」

下面是一个日志记录器的例子。假设我们有文件日志和控制台日志两种实现。

抽象产品

// ILogger.h
#pragma once
#include <string>

class ILogger {
public:
    virtual ~ILogger() = default;
    virtual void log(const std::string& message) = 0;
};

具体产品

// ConsoleLogger.h
#pragma once
#include "ILogger.h"
#include <iostream>

class ConsoleLogger : public ILogger {
public:
    void log(const std::string& message) override {
        std::cout << "[Console] " << message << std::endl;
    }
};

// FileLogger.h
#pragma once
#include "ILogger.h"
#include <fstream>

class FileLogger : public ILogger {
public:
    explicit FileLogger(const std::string& filename) : filename_(filename) {}

    void log(const std::string& message) override {
        std::ofstream file(filename_, std::ios::app);
        if (file.is_open()) {
            file << "[File] " << message << std::endl;
        }
    }

private:
    std::string filename_;
};

工厂类

// LoggerFactory.h
#pragma once
#include "ILogger.h"
#include "ConsoleLogger.h"
#include "FileLogger.h"
#include <memory>
#include <stdexcept>

class LoggerFactory {
public:
    enum class LoggerType {
        Console,
        File
    };

    static std::unique_ptr<ILogger> createLogger(LoggerType type) {
        switch (type) {
            case LoggerType::Console:
                return std::make_unique<ConsoleLogger>();
            case LoggerType::File:
                return std::make_unique<FileLogger>("app.log");
            default:
                throw std::invalid_argument("Unknown logger type");
        }
    }
};

客户端使用

// main.cpp
#include "LoggerFactory.h"

int main() {
    auto logger = LoggerFactory::createLogger(LoggerFactory::LoggerType::Console);
    logger->log("系统启动成功");

    auto fileLogger = LoggerFactory::createLogger(LoggerFactory::LoggerType::File);
    fileLogger->log("写入日志文件");

    return 0;
}

个人习惯:我一般把工厂方法设计成静态的,这样客户端不用先实例化工厂对象。但要注意,静态方法不利于单元测试中的 mock。如果你的项目对可测试性要求高,可以考虑用非静态方法配合依赖注入。

优缺点分析

任何模式都有两面性。简单工厂虽然好用,但也不是万能的。我曾经在一个项目中过度使用它,结果工厂类变得臃肿不堪,维护起来很痛苦。

优点

  • 封装创建逻辑:客户端不需要知道具体产品类的名字,只需要传一个参数。说白了,把「怎么创建」这件事藏起来了。
  • 代码集中管理:所有对象的创建都在一个地方,修改起来方便。比如要改所有日志记录器的构造参数,只需要改工厂类。
  • 降低耦合:客户端只依赖抽象产品接口,不依赖具体实现。这符合依赖倒置原则。

缺点

  • 工厂类职责过重:一旦产品种类增多,工厂类会变得很庞大。我曾经见过一个工厂类里有 30 多个 case 分支,那代码简直没法看。
  • 违反开闭原则:新增产品类型时,必须修改工厂类。这其实违背了「对扩展开放,对修改关闭」的原则。
  • 不适用于产品结构复杂的场景:如果产品对象的构造过程很复杂,需要多个步骤或者依赖其他对象,简单工厂就力不从心了。这时候可以考虑建造者模式。

避坑指南:我曾经在一个电商项目中,用简单工厂创建了 20 多种促销策略。结果每次新增促销活动,都要改工厂类,而且一不小心就改出 bug。后来我重构成了工厂方法模式,每个策略有自己的工厂,才解决了这个问题。所以我的建议是:产品种类不超过 5 个时,用简单工厂很合适;超过 5 个,考虑工厂方法或抽象工厂。

适用场景

场景 说明
产品种类少且稳定 比如日志记录器、数据库连接驱动等,种类固定不变
客户端只需要知道产品接口 客户端不关心具体实现,只关心能不能用
创建逻辑简单 不需要复杂的初始化参数或依赖注入

嗯,简单工厂模式就讲到这里。它虽然简单,但却是理解工厂方法模式和抽象工厂模式的基础。你想想看,如果连对象的创建都管理不好,后面的架构设计就更难展开了。


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