4. 工厂模式(上):简单工厂模式、工厂方法模式、工厂模式在日志框架中的应用

工厂模式,说白了就是帮你「创建对象」的。很多新手觉得这玩意儿多余——new 一下不就完事了吗?我刚开始也这么想。直到有一次我在项目中重构一个支付模块,发现到处都是 new Alipay()new WechatPay(),改一个支付方式要改十几个文件……嗯,从那以后我再也不敢小看工厂模式了。

4.1 为什么需要工厂模式?

你想想看,软件开发里最怕什么?最怕「变化」。今天用 MySQL,明天换 PostgreSQL;今天对接支付宝,明天加微信支付。如果每次变化都要改业务代码,那代码就成了一团乱麻。

工厂模式的核心思想就一句话:把创建对象的逻辑抽出来,交给专门的「工厂」去管。业务代码只管用,不管造。

工厂模式的三个好处:
  • 解耦:调用方不依赖具体类,只依赖接口
  • 复用:创建逻辑集中管理,一处改处处生效
  • 扩展:加新产品不用改旧代码,符合开闭原则

4.2 简单工厂模式

简单工厂模式,其实不算 GoF 的 23 种设计模式之一。但它是最直观、最常用的入门模式。我个人习惯把它叫做「静态工厂」,因为通常用一个静态方法来创建对象。

4.2.1 结构

简单工厂由三部分组成:

  • 工厂类:负责创建产品,通常有一个静态方法
  • 产品接口:所有产品共同实现的接口
  • 具体产品:实现产品接口的各个类

4.2.2 代码示例

假设我们要做一个日志记录器。先定义产品接口:

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

// 具体产品:文件日志
public class FileLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("写入文件:" + message);
    }
}

// 具体产品:数据库日志
public class DatabaseLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("写入数据库:" + message);
    }
}

然后写一个简单工厂:

// 简单工厂
public class LoggerFactory {
    public static Logger createLogger(String type) {
        if ("file".equalsIgnoreCase(type)) {
            return new FileLogger();
        } else if ("database".equalsIgnoreCase(type)) {
            return new DatabaseLogger();
        } else {
            throw new IllegalArgumentException("不支持的日志类型:" + type);
        }
    }
}

调用方就清爽多了:

Logger logger = LoggerFactory.createLogger("file");
logger.log("用户登录成功");
我的经验:简单工厂适合产品种类少、变化不频繁的场景。我在一个内部工具项目里用过,当时只有三种导出格式(PDF、Excel、CSV),简单工厂完全够用。但如果产品种类超过 5 个,或者经常新增,建议升级到工厂方法模式。

4.2.3 简单工厂的缺点

简单工厂最大的问题是什么?违背开闭原则。每次加新产品,都要改工厂类的 if-else。我曾经在一个项目里看到工厂类有 20 多个 if-else,改一个不小心就影响其他分支。

避坑指南:如果你发现工厂类里的 if-else 超过 5 个,或者频繁修改,赶紧重构。别等到 20 个分支再动手,那时候改起来真要命。

4.3 工厂方法模式

工厂方法模式,说白了就是把工厂也抽象化。每个产品对应一个工厂,加新产品就加新工厂,不用改旧代码。

4.3.1 结构

工厂方法模式有四个角色:

  • 抽象工厂:定义创建产品的接口
  • 具体工厂:实现抽象工厂,创建具体产品
  • 抽象产品:产品接口
  • 具体产品:实现产品接口

4.3.2 代码示例

还是日志的例子,这次用工厂方法:

// 抽象工厂
public interface LoggerFactory {
    Logger createLogger();
}

// 具体工厂:文件日志工厂
public class FileLoggerFactory implements LoggerFactory {
    @Override
    public Logger createLogger() {
        return new FileLogger();
    }
}

// 具体工厂:数据库日志工厂
public class DatabaseLoggerFactory implements LoggerFactory {
    @Override
    public Logger createLogger() {
        return new DatabaseLogger();
    }
}

调用方:

LoggerFactory factory = new FileLoggerFactory();
Logger logger = factory.createLogger();
logger.log("用户登录成功");
关键区别:简单工厂用参数决定创建哪个产品,工厂方法用不同的工厂类决定。前者是「传参判断」,后者是「多态分发」。

4.3.3 工厂方法的好处

加新产品时,只需要:

  1. 写一个新的具体产品类
  2. 写一个对应的具体工厂类

旧代码一行都不用改。这就是开闭原则的体现——对扩展开放,对修改关闭。

我记得有一次项目里要加一个「阿里云日志」的输出方式。用工厂方法,我花了 10 分钟写了个 AliyunLoggerAliyunLoggerFactory,然后配置一下注入就完事了。同事看了直呼「优雅」。

4.4 工厂模式在日志框架中的应用

说到日志框架,Java 领域最经典的就是 SLF4J + Logback 的组合。SLF4J 本身就是一个典型的工厂模式应用。

4.4.1 SLF4J 的工厂设计

SLF4J 的核心接口是 Logger,获取 Logger 的方式是:

Logger logger = LoggerFactory.getLogger(MyClass.class);

这里的 LoggerFactory 就是一个工厂类。但它不是简单工厂,也不是纯粹的工厂方法——它用的是绑定模式(Binding),本质上是一种变种的工厂方法。

SLF4J 在编译期不绑定具体实现,运行时通过类路径上的 jar 包来决定用 Logback 还是 Log4j。具体流程:

  1. LoggerFactory.getLogger() 调用
  2. 通过 StaticLoggerBinder 查找具体实现
  3. 返回对应的 Logger 实例
我的理解:SLF4J 的工厂模式,本质上是一个「可插拔的工厂方法」。每个日志实现(Logback、Log4j2)都提供自己的工厂,SLF4J 在运行时动态选择。这种设计非常灵活,也是我推荐大家在框架设计中学习的思路。

4.4.2 自己实现一个迷你日志工厂

为了加深理解,我写一个迷你版的日志工厂:

// 抽象产品
public interface Logger {
    void info(String msg);
    void error(String msg);
}

// 具体产品:控制台日志
public class ConsoleLogger implements Logger {
    @Override
    public void info(String msg) {
        System.out.println("[INFO] " + msg);
    }
    @Override
    public void error(String msg) {
        System.err.println("[ERROR] " + msg);
    }
}

// 具体产品:文件日志
public class FileLogger implements Logger {
    @Override
    public void info(String msg) {
        // 写入文件
    }
    @Override
    public void error(String msg) {
        // 写入文件
    }
}

// 工厂方法模式
public interface LoggerFactory {
    Logger createLogger();
}

public class ConsoleLoggerFactory implements LoggerFactory {
    @Override
    public Logger createLogger() {
        return new ConsoleLogger();
    }
}

public class FileLoggerFactory implements LoggerFactory {
    @Override
    public Logger createLogger() {
        return new FileLogger();
    }
}

调用方通过配置决定用哪个工厂:

// 从配置文件读取工厂类名
String factoryClass = config.getProperty("logger.factory");
LoggerFactory factory = (LoggerFactory) Class.forName(factoryClass).newInstance();
Logger logger = factory.createLogger();
logger.info("系统启动完成");
核心思路:通过配置文件 + 反射,实现运行时动态切换日志实现。这就是 SLF4J 的简化版。

4.5 简单工厂 vs 工厂方法:怎么选?

对比维度 简单工厂 工厂方法
复杂度 低,一个类搞定 中,需要多个工厂类
扩展性 差,加产品要改工厂 好,加产品加新工厂
适用场景 产品少、变化少 产品多、变化频繁
开闭原则 违反 符合

我个人习惯是:先简单,再升级。刚开始产品只有两三个,用简单工厂快速搞定。等产品多了,或者发现频繁改工厂类,再重构到工厂方法。别一开始就上工厂方法,过度设计也是坑。

4.6 本章小结

工厂模式的核心,就是把「创建对象」这件事从业务代码中剥离出来。简单工厂适合小规模场景,工厂方法适合需要频繁扩展的场景。日志框架 SLF4J 是工厂模式在工业界的经典应用,值得反复学习。

嗯,工厂模式的上半部分就到这里。记住一句话:别在业务代码里 new 具体类,把创建权交给工厂。这是你从「会用」到「会设计」的第一步。


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