设计模式-工厂模式

写软件的时候,你很快会发现一个现象:到处都是 new。new 这个类,new 那个类,控制器里 new,Service 里 new,工具类里也 new。刚开始你可能觉得没什么,反正 new 很简单,可是系统一旦复杂起来,你会发现这些 new 带来的问题越来越多:

  1. 想换一个实现,需要全局搜索替换;

  2. 想给对象创建过程加点额外逻辑(比如初始化配置、打日志、做缓存),每个 new 的地方都要改;

  3. 单元测试也很难写,到处都是硬编码的具体类依赖。

leader 看你在项目里到处 new,终于忍不住提醒你:"对象的创建本身也是一种职责,不要乱塞到业务逻辑里。可以考虑用工厂模式,把创建对象这件事单独抽出来管理。"

你于是开始接触"工厂"这一类设计模式,慢慢从"直接 new"进化到"找工厂要东西"。


一、从"直接 new"开始的困境

假设你现在要做一个简单的日志接口,不想绑定具体实现,于是先定义了一个抽象接口:

复制代码
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);
    }
}

业务代码里,你开始到处这么用:

复制代码
Logger logger = new ConsoleLogger();
logger.info("用户登录成功");

后来项目上线了,leader 说要把日志切到文件里,并且以后还有可能接别的日志系统。你只好再写一个 FileLogger:

复制代码
public class FileLogger implements Logger {

    @Override
    public void info(String msg) {
        // 写入文件
    }

    @Override
    public void error(String msg) {
        // 写入文件
    }
}

问题来了:你现在想切换到 FileLogger,就得把所有 new ConsoleLogger() 的地方一一改成 new FileLogger()。如果调用的地方少还好,但在真实项目中,Logger 很可能被使用在大量位置。

leader 看你愁眉苦脸,笑着说:"别直接 new 具体类了,先包一层工厂,后续要切换实现,就从工厂下手就行了。"


二、简单工厂模式:先集中管理"创建哪一种"

你第一步做的事情,就是写了一个"简单工厂":

复制代码
public class LoggerFactory {

    // 简单用一个类型值来区分
    public static Logger createLogger(String type) {
        if ("console".equalsIgnoreCase(type)) {
            return new ConsoleLogger();
        } else if ("file".equalsIgnoreCase(type)) {
            return new FileLogger();
        } else {
            throw new IllegalArgumentException("未知日志类型: " + type);
        }
    }
}

业务代码里,你不再直接 new:

复制代码
Logger logger = LoggerFactory.createLogger("console");
logger.info("用户登录成功");

当你要切到文件日志时,只需要在配置里把 "console" 换成 "file",或者在工厂内部做一点策略逻辑,就可以完成"全局切换":

复制代码
// 伪代码:根据配置决定到底给谁
public static Logger createLogger() {
    String type = readFromConfig();
    if ("file".equalsIgnoreCase(type)) {
        return new FileLogger();
    } else {
        return new ConsoleLogger();
    }
}

这就是最入门、也最常用的一种工厂模式:**简单工厂模式**(有些书上称为"静态工厂方法")。

它解决的问题很明确:

  1. 把"创建哪一种具体产品"的逻辑集中到一个地方;

  2. 调用方只要要一个 Logger,不关心实际返回的是 Console 还是 File。

但是 leader 很快又指出了一个问题:"如果以后再加一种 CloudLogger 呢?每次加新类型,你就要改 LoggerFactory,这其实违反了开闭原则。"

你这才知道,还有一个"工厂方法模式"等着你。


三、工厂方法模式:让子类决定"创建哪一种"

leader 让你把 Logger 的创建逻辑再抽象一层:

复制代码
// 抽象工厂
public interface LoggerFactory {

    Logger createLogger();
}

每种 Logger 对应一个具体工厂:

复制代码
// 控制台日志工厂
public class ConsoleLoggerFactory implements LoggerFactory {

    @Override
    public Logger createLogger() {
        return new ConsoleLogger();
    }
}

// 文件日志工厂
public class FileLoggerFactory implements LoggerFactory {

    @Override
    public Logger createLogger() {
        // 这里还可以顺便做一些初始化工作,比如创建文件目录等
        return new FileLogger();
    }
}

客户端代码变为:

复制代码
public class Client {

    public static void main(String[] args) {
        LoggerFactory factory = new ConsoleLoggerFactory();
        Logger logger = factory.createLogger();
        logger.info("用户登录成功");
    }
}

当你要换成 FileLogger,只需要把工厂换掉:

LoggerFactory factory = new FileLoggerFactory();

这就是**工厂方法模式**的典型做法:**把"创建具体产品对象"的职责延迟到子类中去实现**,这样新增产品时,只需要新增一个工厂实现,而不用去修改已有工厂逻辑。

和简单工厂相比:

  1. 简单工厂把所有判断逻辑写在一个大 if / else 里,新增产品要改这个类;

  2. 工厂方法是"一种产品,对应一个工厂子类",新增产品只需新增工厂,不动原来的代码,更符合开闭原则。


四、再看一个更接近实际业务的例子:支付工厂

假设你在做一个支付模块,支持微信支付、支付宝支付、银联支付。你一开始可能会这样写:

复制代码
public interface PayService {

    void pay(double amount);
}

public class WechatPayService implements PayService {

    @Override
    public void pay(double amount) {
        System.out.println("微信支付:" + amount);
    }
}

public class AlipayService implements PayService {

    @Override
    public void pay(double amount) {
        System.out.println("支付宝支付:" + amount);
    }
}

如果你在业务代码里到处这么干:

复制代码
PayService payService = new WechatPayService();
payService.pay(100);

那当以后要切到支付宝、或者增加银联、Apple Pay 时,你又得满世界搜索替换。

leader 提醒你:"这就是典型的工厂方法模式使用场景,把'要哪种支付'这件事交给工厂。"

你可以这么设计:

复制代码
public interface PayFactory {

    PayService createPayService();
}

public class WechatPayFactory implements PayFactory {

    @Override
    public PayService createPayService() {
        return new WechatPayService();
    }
}

public class AlipayFactory implements PayFactory {

    @Override
    public PayService createPayService() {
        return new AlipayService();
    }
}

客户端:

复制代码
public class PayClient {

    public static void main(String[] args) {
        // 根据配置或用户选择,决定使用哪个工厂
        PayFactory factory = new WechatPayFactory();
        PayService payService = factory.createPayService();
        payService.pay(100);
    }
}

以后你要增加银联支付,只需要:

  1. 新增一个 UnionPayService 实现 PayService;

  2. 新增一个 UnionPayFactory 实现 PayFactory。

原来的代码无需改动。


五、工厂模式的正式定义

你会发现,"工厂模式"其实是一族模式的统称,最常见的是:

  1. 简单工厂(不在 GoF 23 种之内,但非常常用);

  2. 工厂方法(GoF 之一);

  3. 抽象工厂(你前面已经单独写过了)。

如果聚焦在"工厂方法模式",可以给它一个比较严谨的定义:

工厂方法模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类的实例化延迟到其子类。

关键点有两个:

  1. "定义创建对象的接口":抽象工厂接口(比如 LoggerFactory、PayFactory);

  2. "具体实例化逻辑交给子类":ConsoleLoggerFactory、FileLoggerFactory 等。


六、工厂模式的优点和缺点

leader 还是那句话:"设计模式不是背定义,是要知道啥时候用、用了能带来什么好处。"

优点:

  1. **去掉了客户端对具体类的依赖**:客户端只依赖抽象接口和工厂,不直接依赖具体实现,降低耦合度。

  2. **符合开闭原则**:新增一种产品时,只需要新增一个具体工厂和对应产品类,不必改原有代码。

  3. **集中管理创建逻辑**:对象创建过程中的复杂逻辑(参数校验、默认配置、缓存复用等)可以集中放在工厂里。

缺点:

  1. **类数量增多**:一种产品往往要对应一个工厂类,对小项目来说可能略显繁琐。

  2. **结构复杂度上升**:相比"简单 new 一下",工厂模式引入了一层间接,会增加理解成本。

  3. **易与其他创建型模式混淆**:尤其是和抽象工厂、建造者,经常被混用,使用时需要分清关注点。

leader 给你的建议是:**当"创建对象"这件事本身变复杂、或者产品族不断扩展时,就可以考虑引入工厂模式,把"怎么创建"抽离出来管理。**


七、工厂模式与抽象工厂、建造者的关系

你已经写过抽象工厂和建造者的文档,难免会觉得这些模式都在"创建对象",有点傻傻分不清。

leader 帮你梳理了一下思路:

  1. **简单工厂 / 工厂方法**:关注"选择哪一种产品"。比如要 ConsoleLogger 还是 FileLogger,要微信支付还是支付宝支付。

  2. **抽象工厂**:关注"一整套相关产品"的组合。比如同一系列的 Button + Input + ListView,一起切换。

  3. **建造者模式**:关注"复杂产品内部如何一步步组装"。比如一台电脑的 CPU、内存、硬盘、显卡如何搭配。

一句话概括:

  1. 工厂方法:帮你选"哪一款成品";

  2. 抽象工厂:帮你选"一整套成品组合";

  3. 建造者:帮你一步步"配置出一款定制成品"。


八、工厂模式的典型应用场景

最后,你结合自己的经验,总结了一些工厂模式特别适合的场景:

  1. **经常需要根据不同条件创建不同实现时**

比如不同环境(开发 / 测试 / 生产)用不同的配置加载器、不同的日志实现。

  1. **系统对"创建对象的过程"有额外要求时**

比如对象创建前要读配置、做缓存复用、注入依赖、打监控日志等。

  1. **产品族会不断扩展时**

比如支付方式、存储引擎、消息中间件适配等,会随着业务发展增加越来越多实现。

  1. **希望对上层屏蔽具体类细节时**

比如你对外只暴露统一的 `ImageReader` 接口,具体到底用的是 PNGReader 还是 JPGReader 由工厂内部决定。

你这才真正理解 leader 最开始说的那句话:

"当你发现 new 出来的不是'普通小物件',而是以后极有可能不断扩展、更换实现的'关键角色'时,就别急着直接 new 了,给它配一个工厂,长远来看你会省下很多麻烦。"

工厂方法模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类,使一个类的实例化延迟到其子类。

备注:以上内容基于AI生成

相关推荐
han_3 小时前
JavaScript设计模式(四):发布-订阅模式实现与应用
前端·javascript·设计模式
bmseven4 小时前
23种设计模式 - 工厂方法(Factory Method)
设计模式·工厂方法模式
pqq的迷弟4 小时前
设计模式的原则
设计模式
君主黑暗5 小时前
设计模式-抽象工厂模式
设计模式·抽象工厂模式
寰宇的行者5 小时前
设计模式:单例模式
单例模式·设计模式
zjjsctcdl6 小时前
Spring Boot 经典九设计模式全览
java·spring boot·设计模式
君主黑暗7 小时前
设计模式-建造者模式
设计模式·建造者模式
bmseven7 小时前
23种设计模式 - 原型模式(Prototype)
设计模式·原型模式
皙然7 小时前
深度解析 “池化思想”:从设计模式到 Java 技术栈的落地与实践
java·开发语言·设计模式