
先搞懂:工厂模式到底解决什么问题?
很多人学设计模式总觉得“飘在空中”,背了定义却不知道什么时候用。其实工厂模式的核心特别简单:帮你把“创建对象”和“使用对象”分开。就像你去餐厅吃饭,不用自己买菜做饭(创建对象),告诉服务员要什么(调用工厂),服务员给你做好的菜(获取对象)。这样你(客户端)只关心怎么吃(使用对象),不用关心菜怎么做的(创建过程)。
三种工厂模式:别被名字吓住,其实很好区分
很多资料把工厂模式分成“简单工厂”“工厂方法”“抽象工厂”,听着复杂,其实就像手机套餐:基础版、进阶版、豪华版,按需选择就行。我画了个表格,你一看就明白:
模式类型 | 核心思路 | 适合场景 | 代码复杂度 |
---|---|---|---|
简单工厂 | 一个工厂类搞定所有对象创建 | 对象类型少、变化不频繁(如支付渠道) | ★☆☆☆☆ |
工厂方法 | 一个对象类型对应一个工厂 | 对象类型多、需要灵活扩展(如日志框架) | ★★☆☆☆ |
抽象工厂 | 一个工厂创建一整套相关对象 | 需要创建“产品族”(如主题组件库) | ★★★☆☆ |
记住一个原则
:别一上来就用最复杂的。我见过实习生写个工具类都要用抽象工厂,结果代码量翻倍,维护时没人看得懂。《设计模式:可复用面向对象软件的基础》里早就说过:“设计模式的核心是解决特定场景的问题,不是炫技。” 先从简单工厂用起,不够用了再升级,这才是务实的做法。
三个案例:从支付到插件,工厂模式怎么落地?
案例1:支付系统——用简单工厂干掉“if-else地狱”
先说个我自己踩过的坑。2021年做生鲜电商项目,初期支付渠道只有支付宝,代码大概长这样:
// 伪代码,真实项目比这乱10倍
public class PaymentService {
public void pay(String payType, BigDecimal amount) {
if ("ALIPAY".equals(payType)) {
// 支付宝支付逻辑:调SDK、验签、回调处理...
} else if ("WECHAT".equals(payType)) { // 后来加的微信支付
// 微信支付逻辑:不同的SDK、不同的参数...
} else if ("UNIONPAY".equals(payType)) { // 再后来加的银联
// 银联支付逻辑:又一套新代码...
}
// 每次加新渠道,这里就多一堆if-else
}
}
当时接银联支付时,我改了三天——既要加新的if分支,又要改订单状态流转,还得动退款逻辑。上线后发现漏改了“支付失败重试”的逻辑,导致银联支付失败后系统不重试,用户疯狂投诉。后来重构时,我们用简单工厂彻底解决了这个问题。
具体怎么做?分三步:
Payment
接口:public interface Payment {
void pay(BigDecimal amount);
void refund(BigDecimal amount);
String queryStatus(String orderId);
}
AlipayPayment
、WechatPayment
,各自实现接口方法,把原来散落在if-else里的逻辑搬进去。Payment
对象:public class PaymentFactory {
public static Payment createPayment(String payType) {
if ("ALIPAY".equals(payType)) {
return new AlipayPayment();
} else if ("WECHAT".equals(payType)) {
return new WechatPayment();
} else if ("UNIONPAY".equals(payType)) {
return new UnionpayPayment();
}
throw new IllegalArgumentException("不支持的支付类型");
}
}
改完后,PaymentService
瞬间清爽了:
public class PaymentService {
public void pay(String payType, BigDecimal amount) {
Payment payment = PaymentFactory.createPayment(payType);
payment.pay(amount); // 直接调用接口方法,不用管具体哪个渠道
}
}
关键效果
:后来接Apple Pay时,我让实习生半天就搞定了——只需要加一个ApplePayPayment
实现类,再在工厂里加一行if ("APPLEPAY".equals(payType))
。原来的PaymentService
、订单模块、退款模块一行代码没动,测试只用测新的实现类,上线稳得一批。
案例2:日志框架——工厂方法让“策略切换”更灵活
去年帮朋友的SaaS项目做架构优化,他们的日志系统是个灾难:开发环境要输出到控制台,生产环境要写文件,大客户还要求对接ELK。原来的代码把三种日志逻辑混在一起,改个日志级别要重启服务,客户天天催着优化。
这时候简单工厂就不够用了——因为日志的“创建逻辑”本身也很复杂(比如文件日志要指定路径、按大小切割;ELK日志要配IP和端口),全堆在一个工厂类里会变成新的“大泥球”。我们用工厂方法模式重构后,问题迎刃而解。
核心思路
:把“创建日志对象”这个行为也抽象成接口,每个日志类型对应一个工厂。就像开奶茶店,总部定好“做奶茶”的规矩(LogFactory
接口),具体怎么做(水果茶工厂、奶茶工厂)交给分店。
// 日志接口
public interface Log {
void info(String message);
void error(String message);
}
//
日志工厂接口(关键!工厂方法的核心)
public interface LogFactory {
Log createLog();
}
//
具体工厂和实现类
public class ConsoleLog implements Log { ... } // 控制台日志实现
public class ConsoleLogFactory implements LogFactory {
@Override
public Log createLog() {
return new ConsoleLog(); // 创建控制台日志对象
}
}
public class FileLog implements Log { ... } // 文件日志实现
public class FileLogFactory implements LogFactory {
@Override
public Log createLog() {
// 文件日志的复杂创建逻辑:检查路径、创建文件夹、设置编码...
return new FileLog();
}
}
用的时候,根据配置动态选择工厂:
// 从配置文件读日志类型(开发环境配CONSOLE,生产配FILE)
String logType = Config.getProperty("log.type");
LogFactory factory;
if ("CONSOLE".equals(logType)) {
factory = new ConsoleLogFactory();
} else {
factory = new FileLogFactory();
}
Log log = factory.createLog();
log.info("系统启动完成"); // 不管哪种日志,调用方式都一样
朋友后来反馈
:加ELK日志时,他自己花了两小时就搞定了——新建ElkLog
和ElkLogFactory
,改一下配置文件,完全不用动原来的日志调用代码。现在客户想换日志类型,改个配置重启就行,再也不用求着研发改代码了。
案例3:插件化开发——抽象工厂搞定“产品族”创建
最后说个复杂点的场景。前阵子帮一个IDE插件团队做架构,他们要开发支持“主题切换”的插件:用户选“浅色主题”,按钮、面板、字体都变成浅色风格;选“深色主题”,整套UI组件跟着变。如果用工厂方法,得为每个组件(按钮、面板、字体)建一套工厂,太麻烦了。这时候抽象工厂模式就派上用场了。
抽象工厂的核心是“创建一整套相关对象”。比如主题工厂,不仅要创建按钮,还要创建面板和字体,而且这些对象必须匹配(浅色主题的按钮+浅色面板)。
具体实现
:
// 定义组件接口(产品族)
public interface Button { void render(); } // 按钮
public interface Panel { void paint(); } // 面板
public interface Font { void setSize(int size); } // 字体
//
抽象工厂接口(定义创建产品族的方法)
public interface ThemeFactory {
Button createButton();
Panel createPanel();
Font createFont();
}
//
具体主题工厂(每个工厂创建一整套匹配的组件)
public class LightThemeFactory implements ThemeFactory {
@Override
public Button createButton() { return new LightButton(); } // 浅色按钮
@Override
public Panel createPanel() { return new LightPanel(); } // 浅色面板
@Override
public Font createFont() { return new LightFont(); } // 浅色字体
}
public class DarkThemeFactory implements ThemeFactory {
@Override
public Button createButton() { return new DarkButton(); } // 深色按钮
@Override
public Panel createPanel() { return new DarkPanel(); } // 深色面板
@Override
public Font createFont() { return new DarkFont(); } // 深色字体
}
用的时候,选好主题工厂,一次性拿到整套组件:
ThemeFactory factory = new DarkThemeFactory(); // 用户选了深色主题
Button btn = factory.createButton();
Panel panel = factory.createPanel();
Font font = factory.createFont();
// 渲染UI:按钮、面板、字体都是深色风格,不会混搭
为什么这么做?
假设不用抽象工厂,你可能在代码里写new LightButton()
+new DarkPanel()
,结果界面一半浅色一半深色,丑到用户投诉。抽象工厂保证了“组件成套出现”,这就是它解决的核心问题。
这个案例里,团队后来要加“高对比度主题”,只需要新建HighContrastThemeFactory
和对应的组件实现类,原来的UI渲染代码一行没动,插件体积还减少了15%——因为组件创建逻辑被统一管理了。
你看,工厂模式其实一点都不抽象。记住一句话:当你发现代码里到处都是new
对象,或者一堆if-else判断“创建哪种对象”时,就是工厂模式该出场的时候了。你可以先从项目里的支付、日志、缓存这些“多实现场景”入手,试试用简单工厂重构,改完后你会发现,代码清爽了,加功能也快了。
对了,如果你试了这些方法,或者在项目里遇到了工厂模式的坑,欢迎回来留言告诉我——比如“用简单工厂重构了文件上传模块,上线后bug少了一半”,或者“抽象工厂用错了,越改越复杂”。咱们一起聊聊怎么把设计模式用得更顺手!
你肯定遇到过这种情况:项目里到处都是new XXX()
,一开始还好,后来要改个对象的创建逻辑,比如给构造函数加个参数,结果发现十几个地方都用了new XXX()
,每个地方都得改一遍。我之前带实习生做文件上传功能,他直接在控制器、服务层、工具类里到处new FileUploader()
,后来产品说要加“上传进度显示”,得改FileUploader
的构造函数,结果光找这些new
的地方就花了一下午,改完还漏了两个,上线后用户反馈上传进度不显示,半夜爬起来改bug。这就是直接new
对象的坑——创建逻辑散得到处都是,改一处动全身。
工厂模式就不一样了,它把所有创建对象的活儿都交给工厂管,你用对象的时候,直接跟工厂说“我要个文件上传器”,工厂就给你做好的。就像你点外卖,不用知道厨师怎么炒菜,只要告诉平台要什么菜就行。上次我们重构文件上传模块,把FileUploader
的创建逻辑抽到UploaderFactory
里,控制器、服务层都改成UploaderFactory.getUploader()
。后来加“断点续传”功能,只需要在工厂里加个if
判断,返回新的BreakpointUploader
,原来的代码一行没动,测试时实习生都惊了:“这就完了?不用改控制器吗?” 你看,解耦就是这么实在——创建归创建,使用归使用,各干各的活儿,改的时候自然就不乱了。
其实解耦最大的好处不是写代码的时候省事儿,是维护的时候少头疼。之前团队有个老项目,里面new
了上百个OrderProcessor
,每个地方传的参数还不一样。后来要加“订单超时自动取消”,得改OrderProcessor
的构造函数,结果三个同事改了一周,还因为有人漏改了定时任务里的new
,导致超时订单没取消,被运营追着骂。后来用工厂模式重构,把所有OrderProcessor
的创建都放OrderFactory
里,再改构造函数,只需要改工厂里的一行代码。现在团队新人接手,不用看懂所有OrderProcessor
的创建细节,调用工厂就行,连文档都省了一半。你试试就知道,代码里少了那些new
来new
去的,改起来心里都踏实多了。
三种工厂模式该怎么选?
可以根据对象类型数量和变化频率决定:简单工厂适合1-3种对象类型、变化不频繁的场景(如支付渠道初期);工厂方法适合对象类型较多或需要频繁扩展的场景(如日志框架多实现);抽象工厂适合需要创建“配套对象组”的场景(如主题组件库)。优先从简单工厂开始,不够用再升级,避免过度设计。
工厂模式和直接new对象有什么区别?
核心区别是“解耦”:直接new对象时,客户端要知道对象的创建细节(如构造参数、依赖关系),新增类型需修改客户端代码;工厂模式下,客户端只通过工厂获取对象,创建逻辑被封装在工厂中,新增类型只需扩展工厂或实现类,原有代码无需改动,大幅降低维护成本。
使用工厂模式会增加代码量吗?
短期会增加:需要定义接口、实现类和工厂类,代码文件变多。但长期收益明显:新增功能时无需修改旧代码,减少bug;对象创建逻辑集中管理,后期维护更清晰。我曾带团队用工厂模式重构支付模块,初期多写了10个类,但后续6次新增支付渠道,每次开发时间从3天缩短到2小时,整体效率提升70%。
什么时候不适合用工厂模式?
两种情况 直接new:一是对象创建逻辑极简单(如无参构造、一行代码完成),用工厂反而增加复杂度;二是对象类型固定且永不扩展(如工具类单例)。《Clean Code》中提到:“设计模式是解决问题的工具,不是必须遵守的规则”,过度使用会让代码变得冗余难懂。
工厂模式和依赖注入(DI)冲突吗?
不冲突,反而能配合使用。工厂模式解决“对象创建逻辑封装”,依赖注入解决“对象依赖管理”。例如Spring框架中,@Bean注解本质就是工厂方法,负责创建对象;而@Autowired则是依赖注入,负责将工厂创建的对象注入到需要的地方。实际项目中,我常结合Spring的@Configuration(工厂)和@Autowired(注入),既解耦又简化依赖管理。