30、重构与设计模式(三):用工厂模式重构对象创建逻辑
对象创建,听起来是个小问题对吧?
但我在项目里见过太多因为“new”关键字引发的血案。一个类被到处 new,后来构造函数加了参数,好家伙,几十个地方都要改。你想想看,这得多崩溃。
工厂模式就是来解决这个问题的。说白了,它把“创建对象”这件事单独拎出来,交给一个专门的“工厂”去管。你只管要对象,别管它是怎么造出来的。
为什么需要工厂模式?
我先讲个真实案例。几年前我接手一个支付系统,里面处理不同支付方式:微信、支付宝、银联。最初的代码长这样:
public class PaymentService {
public void pay(String type, double amount) {
if ("wechat".equals(type)) {
WechatPay wechat = new WechatPay();
wechat.pay(amount);
} else if ("alipay".equals(type)) {
Alipay alipay = new Alipay();
alipay.pay(amount);
} else if ("unionpay".equals(type)) {
UnionPay union = new UnionPay();
union.pay(amount);
}
}
}
这段代码有什么问题?
- 耦合太紧:PaymentService 直接依赖了所有支付类的具体实现。
- 违反开闭原则:加一种支付方式,就得改 PaymentService 的代码。
- 重复代码:每个 if 分支里都在做“创建对象”这件事。
我当时看着这段代码,心里只有一个念头:必须重构。
简单工厂模式:最直接的解法
我们先来个最简单的方案。把创建对象的逻辑抽到一个工厂类里:
public class PaymentFactory {
public static Payment create(String type) {
if ("wechat".equals(type)) {
return new WechatPay();
} else if ("alipay".equals(type)) {
return new Alipay();
} else if ("unionpay".equals(type)) {
return new UnionPay();
}
throw new IllegalArgumentException("未知支付类型: " + type);
}
}
// 使用方
public class PaymentService {
public void pay(String type, double amount) {
Payment payment = PaymentFactory.create(type);
payment.pay(amount);
}
}
嗯,这样好多了。PaymentService 不再关心具体创建细节,它只依赖 Payment 接口和 PaymentFactory。
核心要点:简单工厂把“创建”和“使用”分离了。但注意,它仍然没有完全解决开闭原则的问题——加新类型还是要改工厂类。
工厂方法模式:让子类决定创建什么
如果你觉得简单工厂还不够灵活,那就上工厂方法模式。它的思路是:定义一个创建对象的接口,让子类决定实例化哪个类。
// 工厂接口
public interface PaymentFactory {
Payment createPayment();
}
// 具体工厂
public class WechatPayFactory implements PaymentFactory {
@Override
public Payment createPayment() {
return new WechatPay();
}
}
public class AlipayFactory implements PaymentFactory {
@Override
public Payment createPayment() {
return new Alipay();
}
}
// 使用方
public class PaymentService {
private PaymentFactory factory;
public PaymentService(PaymentFactory factory) {
this.factory = factory;
}
public void pay(double amount) {
Payment payment = factory.createPayment();
payment.pay(amount);
}
}
这样做的好处很明显:加一种支付方式,只需要新增一个工厂类和对应的支付类,完全不用改现有代码。
我的经验:工厂方法模式适合那些“创建逻辑可能变化”的场景。我曾经在一个报表系统里用过它,不同格式的报表(PDF、Excel、CSV)各自对应一个工厂,后期加 HTML 格式时,零改动就接进去了。
抽象工厂模式:创建一系列相关对象
有时候,你要创建的不是单个对象,而是一组相关的对象。比如一个 UI 组件库,有按钮、输入框、弹窗。不同主题(浅色、深色)下,这些组件的样式不同。
// 抽象工厂
public interface UIFactory {
Button createButton();
Input createInput();
Dialog createDialog();
}
// 浅色主题工厂
public class LightUIFactory implements UIFactory {
@Override
public Button createButton() {
return new LightButton();
}
@Override
public Input createInput() {
return new LightInput();
}
@Override
public Dialog createDialog() {
return new LightDialog();
}
}
// 深色主题工厂
public class DarkUIFactory implements UIFactory {
@Override
public Button createButton() {
return new DarkButton();
}
@Override
public Input createInput() {
return new DarkInput();
}
@Override
public Dialog createDialog() {
return new DarkDialog();
}
}
抽象工厂模式保证了你创建的一组对象是“配套”的——不会出现浅色按钮配深色输入框这种尴尬情况。
三种工厂模式的对比
| 模式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 简单工厂 | 创建逻辑简单,类型较少 | 实现简单,客户端代码干净 | 违反开闭原则,工厂类职责过重 |
| 工厂方法 | 创建逻辑可能变化,需要扩展 | 符合开闭原则,扩展性好 | 类数量增多,结构变复杂 |
| 抽象工厂 | 需要创建一系列相关对象 | 保证产品族一致性 | 增加新产品族困难 |
什么时候不该用工厂模式?
别以为工厂模式是万能的。我见过有人为了一个简单的 User 对象也搞个工厂,这就过度设计了。
避坑指南:我曾经在一个小项目里滥用工厂模式,结果代码量翻了一倍,可维护性反而下降了。记住,工厂模式是为了解决“变化”和“复杂创建”的问题。如果只是 new 一下,没有额外逻辑,就别折腾了。
什么时候该用?
- 创建对象需要复杂逻辑(比如从配置、数据库读取参数)
- 创建的对象可能变化(比如支持多种实现)
- 想隐藏创建细节(比如不想让调用方知道具体类名)
重构步骤总结
如果你现在有一段代码需要引入工厂模式,我建议按这个步骤来:
- 识别变化点:找到那些被到处 new 的类,看看它们是不是有多个变体。
- 提取接口:让所有变体实现同一个接口或继承同一个抽象类。
- 创建工厂:根据场景选择简单工厂、工厂方法或抽象工厂。
- 替换调用点:把所有的 new 替换成工厂调用。
- 测试:确保行为不变。
最后,我用一张图来总结工厂模式的核心思想:
工厂模式不是什么高深的技术,它就是一个“封装变化”的实践。把容易变的部分(对象创建)包起来,让稳定的部分(业务逻辑)不受影响。这个思路,其实贯穿了整个设计模式体系。
一句话总结:工厂模式不是让你多写代码,而是让你把代码写在正确的地方。把变化关进笼子里,你的系统才能经得起折腾。