工厂方法模式:日志记录器工厂

说实话,工厂方法模式是我在实际项目中用得最多的创建型模式之一。为什么?因为它解决了一个很实在的问题——你不想在代码里写死具体类

我记得刚入行那会儿,写代码都是直接 new 对象。后来维护一个老系统,发现到处是 new MySQLDriver()new Log4jLogger()。老板说换 PostgreSQL,我改了一整天的代码。嗯,从那以后,我再也不敢硬编码了。

模式定义:一句话说清楚

工厂方法模式的定义其实很简洁:定义一个创建对象的接口,但让子类决定实例化哪个类。说白了,就是把「new 什么」这件事延迟到子类去处理。

你想想看,如果所有创建逻辑都堆在一个地方,那叫简单工厂。但简单工厂有个毛病——每次加新产品,你都得改那个工厂类。开闭原则就 violated 了。工厂方法模式把创建逻辑拆到各个子工厂里,加新产品只需要加新子类,不用改已有代码。

业务场景:日志记录器工厂

咱们课程里选的场景是「日志记录器工厂」。为什么选这个?因为我在项目中遇到过太多次日志框架切换的问题。

早期项目用 Log4j,后来公司统一要求用 Logback。如果代码里到处都是 Logger.getLogger() 这种 Log4j 的 API,那迁移起来简直要命。工厂方法模式正好解决这个问题——我们定义一个抽象的日志记录器接口,然后让 Log4j 和 Logback 各自实现自己的工厂。

核心结构

先看一张图,把整体结构理清楚:

LoggerFactory(抽象工厂) + createLogger(): Logger Log4jLoggerFactory + createLogger(): Log4jLogger LogbackLoggerFactory + createLogger(): LogbackLogger Logger(抽象产品) + log(message: String): void Log4jLogger + log(): 使用Log4j实现 LogbackLogger + log(): 使用Logback实现

这张图里,LoggerFactory 是抽象工厂,定义了一个 createLogger() 方法。两个具体工厂各自实现这个方法,返回不同的 Logger 实例。客户端只跟 LoggerFactory 和 Logger 接口打交道,具体是 Log4j 还是 Logback,它根本不知道。

代码实现:一步一步来

先定义抽象产品——Logger 接口:

public interface Logger {
    void log(String message);
}

然后是两个具体产品:

public class Log4jLogger implements Logger {
    @Override
    public void log(String message) {
        // 这里调用 Log4j 的原生 API
        System.out.println("[Log4j] " + message);
    }
}

public class LogbackLogger implements Logger {
    @Override
    public void log(String message) {
        // 这里调用 Logback 的原生 API
        System.out.println("[Logback] " + message);
    }
}

接下来是抽象工厂:

public abstract class LoggerFactory {
    public abstract Logger createLogger();
}

具体工厂实现:

public class Log4jLoggerFactory extends LoggerFactory {
    @Override
    public Logger createLogger() {
        return new Log4jLogger();
    }
}

public class LogbackLoggerFactory extends LoggerFactory {
    @Override
    public Logger createLogger() {
        return new LogbackLogger();
    }
}

客户端使用:

public class Client {
    public static void main(String[] args) {
        // 通过配置文件或参数决定用哪个工厂
        LoggerFactory factory = new Log4jLoggerFactory();
        Logger logger = factory.createLogger();
        logger.log("这是一条日志");
    }
}

关键点:客户端代码里没有出现 Log4jLoggerLogbackLogger 这些具体类名。它只依赖 LoggerFactoryLogger 这两个抽象。哪天要切换日志框架,只需要换一个工厂实现,客户端代码一行都不用改。

避坑指南

我曾经在一个项目中犯过这样的错误——工厂方法里做了太多事情。比如在 createLogger() 里不仅创建 Logger,还读取配置文件、初始化连接池。结果每次调用工厂方法都特别慢,而且出了问题很难排查。

记住:工厂方法只负责创建对象。初始化、配置这些事,交给别的方法去做,或者用 Builder 模式来处理。

工厂方法 vs 简单工厂

很多初学者会搞混这两个概念。我简单说一下区别:

对比项 简单工厂 工厂方法
创建逻辑位置 集中在一个工厂类 分散在各个子工厂
扩展性 加产品要改工厂类 加产品只需加子类
适用场景 产品种类少且稳定 产品种类多或经常变化

说白了,简单工厂适合「你知道不会怎么变」的场景。工厂方法适合「你预感以后会加东西」的场景。我个人习惯是:拿不准的时候,直接用工厂方法。因为从简单工厂重构到工厂方法,改动量其实不小。

实际项目中的应用

我记得去年做一个数据迁移工具,需要支持 MySQL 和 PostgreSQL。如果用简单工厂,每次加数据库都要改工厂类。我用了工厂方法模式,每个数据库驱动对应一个工厂。后来客户说要加 Oracle 支持,我花了半小时写了个 OracleDriverFactory,完事。

再比如日志框架适配,这个场景太常见了。很多公司内部有统一的日志规范,但底层实现可能是 Log4j、Logback 或者 SLF4J。用工厂方法模式,你可以写一个 LoggerFactory 接口,然后每个日志框架一个实现。切换的时候,改一行配置就行。

小技巧:工厂方法模式经常和配置文件结合使用。比如在 application.properties 里写 logger.factory=com.example.Log4jLoggerFactory,然后用反射加载。这样连代码都不用改,改个配置文件就能切换实现。

总结

工厂方法模式的核心价值就一句话:把创建对象的职责从客户端剥离出来,交给专门的子工厂。这样做的好处是:

  • 客户端代码只依赖抽象,不依赖具体实现
  • 新增产品类型不需要修改已有代码
  • 每个工厂职责单一,只负责创建一种产品

嗯,其实设计模式这东西,说白了就是「前人踩过的坑总结出来的经验」。工厂方法模式解决的就是「new 对象太死板」这个问题。你在项目中遇到类似场景,不妨试试这个模式。


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