简单工厂模式:静态工厂方法、产品接口、具体产品,以及日志记录器创建案例

简单工厂模式,说白了就是用一个「工厂」帮你把对象创建的事全包了。你只需要告诉工厂你要什么,它给你造出来就行。我刚开始接触设计模式时,觉得这玩意儿太简单了,不就是把new关键字挪了个地方吗?后来在项目中踩过坑才明白——嗯,这背后其实藏着不少门道。

什么是简单工厂模式

简单工厂模式属于创建型模式,但它并不是GoF 23种设计模式中的一员。我个人习惯把它看作「编程入门级」的创建模式。它的核心思想是:用一个单独的工厂类来封装对象的创建逻辑

你想想看,如果代码里到处是new XXX(),哪天产品说「我要换一个实现」,你得满世界找这些new语句。简单工厂就是把这些new集中到一处管理。

核心三要素:

  • 产品接口(Product):定义产品的公共行为
  • 具体产品(Concrete Product):实现产品接口的多个类
  • 工厂类(Factory):包含静态方法,根据参数返回不同的产品实例

为什么叫「静态工厂方法」

因为工厂类里的创建方法通常是static的。你不需要new工厂对象,直接Factory.createProduct(type)就能拿到产品。我见过不少团队把这种模式直接叫做「静态工厂」,其实也没毛病。

这里有个小坑——静态方法不能被继承,也不能被重写。所以如果你未来需要扩展工厂行为,简单工厂就不太够用了。我曾经在一个项目中用简单工厂管理数据库连接,后来要支持多种数据库动态切换,不得不重构成了工厂方法模式。这个教训让我记住了:简单工厂适合产品种类固定、变化不频繁的场景。

日志记录器案例:从需求到实现

咱们用一个日志记录器的例子来走一遍。假设你的系统需要支持三种日志输出:控制台、文件、数据库。用户通过配置文件决定用哪一种。

先定义产品接口:

// 产品接口
public interface Logger {
    void log(String message);
}

然后写三个具体产品:

// 具体产品:控制台日志
public class ConsoleLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("[Console] " + message);
    }
}

// 具体产品:文件日志
public class FileLogger implements Logger {
    @Override
    public void log(String message) {
        // 实际项目中这里会写文件
        System.out.println("[File] " + message);
    }
}

// 具体产品:数据库日志
public class DatabaseLogger implements Logger {
    @Override
    public void log(String message) {
        // 实际项目中这里会写数据库
        System.out.println("[Database] " + message);
    }
}

接下来是核心——工厂类:

// 简单工厂
public class LoggerFactory {
    // 静态工厂方法
    public static Logger createLogger(String type) {
        if (type == null || type.isEmpty()) {
            return null;
        }
        
        switch (type.toLowerCase()) {
            case "console":
                return new ConsoleLogger();
            case "file":
                return new FileLogger();
            case "database":
                return new DatabaseLogger();
            default:
                throw new IllegalArgumentException("不支持的日志类型: " + type);
        }
    }
}

客户端使用起来就很简单了:

public class Client {
    public static void main(String[] args) {
        // 从配置文件读取类型
        String logType = "file";
        
        Logger logger = LoggerFactory.createLogger(logType);
        logger.log("系统启动成功");
    }
}

我的习惯:在实际项目中,我不会把类型字符串硬编码在代码里。通常我会从配置文件(properties、yaml或数据库)中读取,这样切换日志方式只需要改配置,不用改代码。

简单工厂的优缺点

优点 缺点
将对象的创建和使用分离,客户端不需要知道具体类名 工厂类职责过重,新增产品需要修改工厂类
代码集中管理,便于统一维护 违反了开闭原则(对扩展开放,对修改关闭)
适合产品种类较少的场景 产品过多时,工厂方法会变得臃肿

避坑指南

我曾经接手过一个老项目,里面有一个工厂类,switch分支写了二十多个case。每次新增产品都要改这个类,而且经常有人忘记加break导致bug。后来我实在受不了,用反射+配置文件重构了它。

所以我的建议是:

  • 产品种类少于5个时,用简单工厂很舒服
  • 超过5个,考虑用工厂方法模式或反射机制
  • 如果产品经常变化,别用简单工厂,否则你会被频繁修改工厂类逼疯

知识体系图

下面这张图帮你理清简单工厂模式的核心结构:

简单工厂模式结构图 客户端 (Client) 调用工厂方法 LoggerFactory 返回产品 <<接口>> Logger ConsoleLogger FileLogger DatabaseLogger 实现 实现 实现

C++版本实现

如果你用C++,思路完全一样,只是语法不同。这里给个简版:

// 产品接口
class Logger {
public:
    virtual ~Logger() {}
    virtual void log(const std::string& message) = 0;
};

// 具体产品
class ConsoleLogger : public Logger {
public:
    void log(const std::string& message) override {
        std::cout << "[Console] " << message << std::endl;
    }
};

// 工厂类
class LoggerFactory {
public:
    static Logger* createLogger(const std::string& type) {
        if (type == "console") return new ConsoleLogger();
        if (type == "file") return new FileLogger();
        if (type == "database") return new DatabaseLogger();
        return nullptr;
    }
};

注意:C++中工厂返回的是裸指针,记得在客户端用智能指针管理生命周期,否则容易内存泄漏。我见过太多C++新手在简单工厂上栽跟头——new出来的对象忘了delete。

什么时候用简单工厂

我个人总结了三用三不用:

  • :产品种类少且稳定
  • :客户端不需要知道具体产品类名
  • :创建逻辑简单,不需要复杂初始化
  • 不用:产品经常新增或删除
  • 不用:产品创建过程复杂,需要依赖其他对象
  • 不用:希望遵循开闭原则

说白了,简单工厂就是设计模式里的「入门款」。它简单、直观、好用,但别指望它能解决所有创建问题。我建议你在小项目或模块内部使用它,大型框架层面还是考虑更灵活的模式。


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