在云栈社区的技术分享中,我们常常探讨如何让代码更优雅、更易维护。今天,我们来聊聊一个能有效帮你管理对象创建、告别混乱if-else的经典模式——工厂模式。
它的核心思想非常简单,就是把对象创建的逻辑,从业务代码里抽出来,交给专门的工厂类。你的业务代码只需要告诉工厂“我要什么”,就能拿到现成的对象,只负责用,不操心怎么造。这就好比点外卖,你只管下单,厨房怎么做、怎么打包,你完全不用管。
为什么要用工厂模式?
直接在业务代码里随处 new 对象,看似直接,实则暗藏两个“坑”:
- 创建逻辑重复且分散:同一个类的
new 操作,可能在 Controller、Service、定时任务里到处都是。哪天这个类的构造方式需要调整(比如初始化时要多传一个参数),你就得把代码里所有 new 的地方翻出来改一遍,费时费力还容易遗漏。
- 业务代码变臃肿:核心业务逻辑里混入了一堆判断类型、组装参数、创建对象的代码,可读性变差,后期定位问题也像大海捞针。
工厂模式的价值,正是为了解决这些问题。它将变化的部分(对象创建)封装起来,让稳定的部分(业务逻辑)保持干净。
一、简单工厂模式:快速上手的“万能工厂”
简单工厂的核心是 一个工厂类,包揽所有产品的创建。它非常适合产品类型固定、后期很少新增或变更的场景。
1. 定义抽象产品:日志接口
首先,我们定义一个所有日志实现都必须遵守的契约(接口),确保行为一致。
public interface Logger {
void log(String message);
}
2. 实现具体产品:两种日志
分别实现控制台日志和文件日志,每个类职责单一。
// 控制台日志
public class ConsoleLogger implements Logger {
@Override
public void log(String message) {
System.out.println("[控制台日志] " + message);
}
}
// 文件日志(实际项目会包含真实的文件操作,此处为简化演示)
public class FileLogger implements Logger {
@Override
public void log(String message) {
System.out.println("[文件日志] " + message);
}
}
3. 核心工厂:集中管理创建逻辑
这是简单工厂的核心,所有的创建判断都集中在此。
public class LoggerFactory {
// 静态方法,直接调用,无需实例化工厂
public static Logger getLogger(String type) {
if ("console".equals(type)) {
return new ConsoleLogger();
} else if ("file".equals(type)) {
return new FileLogger();
}
// 不支持的类型,直接抛异常快速失败
throw new IllegalArgumentException("不支持的日志类型:" + type);
}
}
4. 客户端调用
业务代码变得非常简洁。
public class SimpleFactoryDemo {
public static void main(String[] args) {
Logger consoleLog = LoggerFactory.getLogger("console");
consoleLog.log("用户登录成功,ID:10086");
Logger fileLog = LoggerFactory.getLogger("file");
fileLog.log("订单支付完成,编号:20260306001");
}
}
运行结果:
[控制台日志] 用户登录成功,ID:10086
[文件日志] 订单支付完成,编号:20260306001
简单工厂的优劣势与适用场景
- 优点:代码简单直观,集中管理创建逻辑,有效减少了重复代码。客户端完全不用关心对象是如何被创建出来的。
- 缺点:违反了“开闭原则”。如果想新增一种日志(比如数据库日志),就必须修改
LoggerFactory 类中的 if-else 逻辑,这可能会影响原有的稳定代码。
- 适用场景:产品类型固定,后期几乎不会新增的基础组件。例如,项目初期确定的少数几种第三方服务客户端、固定的加密算法实现等。
二、工厂方法模式:支持扩展的“专业产线”
简单工厂的缺点(改代码才能加产品)在需要频繁扩展的系统里会很头疼。工厂方法模式正是为此而生,其核心思想是 一个产品对应一个工厂,将对象的创建延迟到具体的工厂子类中去完成。
我们延续日志的例子,目标是:新增一个数据库日志,完全不需要修改任何现有代码。
1. 复用原有产品
Logger 接口、ConsoleLogger、FileLogger 这些现有代码完全不用动。
2. 新增产品:数据库日志
只需新增这个产品类。
// 新增的数据库日志实现
public class DbLogger implements Logger {
@Override
public void log(String message) {
System.out.println("[数据库日志] " + message);
}
}
3. 定义抽象工厂:规范创建行为
不再是具体的类,而是一个接口,声明创建方法。
// 抽象日志工厂,只声明创建方法
public interface LoggerFactory {
Logger createLogger();
}
4. 实现具体工厂:各司其职
每个产品都有自己专属的工厂。
// 控制台日志工厂
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 DbLoggerFactory implements LoggerFactory {
@Override
public Logger createLogger() {
return new DbLogger();
}
}
5. 客户端调用:面向接口编程
public class FactoryMethodDemo {
public static void main(String[] args) {
// 控制台日志
LoggerFactory consoleFactory = new ConsoleLoggerFactory();
Logger consoleLog = consoleFactory.createLogger();
consoleLog.log("系统启动完成,端口:8080");
// 新增的数据库日志
LoggerFactory dbFactory = new DbLoggerFactory();
Logger dbLog = dbFactory.createLogger();
dbLog.log("用户注册成功,手机号:13800138000");
}
}
工厂方法模式严格遵循了“开闭原则”,新增产品时,你只需要增加“产品类+工厂类”,原有代码丝毫无需改动,扩展性极佳,也更安全。当然,它的代价是会产生大量的工厂类,在设计模式的运用中需要权衡。
适用场景:产品类型需要频繁新增或变更,且项目非常注重可扩展性和维护性的中大型系统。
三、实战指南:如何选择这两种模式?
了解了原理,在实际的Java项目中该如何选择呢?一张表帮你快速决策:
| 对比项 |
简单工厂模式 |
工厂方法模式 |
| 核心思想 |
一个“万能”工厂集中创建 |
一个产品对应一个“专业”工厂 |
| 新增产品 |
必须修改工厂类的创建逻辑(如if-else) |
仅需新增产品类和工厂类,不修改老代码 |
| 代码复杂度 |
低,结构简单,适合快速开发 |
高,类数量增多,适合规范开发 |
| 设计原则 |
违反开闭原则 |
符合开闭原则 |
| 适用场景 |
产品类型固定,后期很少变更 |
产品类型频繁新增,系统注重可扩展性 |
结合日常开发场景:
- 支付模块:项目初期只有微信、支付宝支付,为了快速上线,可以采用简单工厂。后续业务扩展,需要接入银联、云闪付等多种支付渠道时,就应该重构升级为工厂方法模式,以应对未来的变化。
- 消息推送中心:一开始只支持短信和微信模板消息,用简单工厂足矣。当产品要求增加邮件、APP站内信、钉钉机器人等推送方式时,工厂方法模式就能让扩展变得轻松且安全。
实际上,Spring框架的核心容器 BeanFactory / ApplicationContext 就是一个超级强化版的工厂,它负责创建、组装和管理我们应用中的所有Bean对象,是工厂模式在框架层面的极致体现。
总结
工厂模式的核心价值在于解耦——将对象的创建与使用分离。无论选择简单工厂还是工厂方法,目的都是让业务逻辑更清晰、代码更易维护、系统更具弹性。下次当你在代码中看到即将失控的 if-else 或 switch 时,不妨考虑一下,是否可以用工厂模式来优雅地解决它。