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 一下,没有额外逻辑,就别折腾了。

什么时候该用?

  • 创建对象需要复杂逻辑(比如从配置、数据库读取参数)
  • 创建的对象可能变化(比如支持多种实现)
  • 想隐藏创建细节(比如不想让调用方知道具体类名)

重构步骤总结

如果你现在有一段代码需要引入工厂模式,我建议按这个步骤来:

  1. 识别变化点:找到那些被到处 new 的类,看看它们是不是有多个变体。
  2. 提取接口:让所有变体实现同一个接口或继承同一个抽象类。
  3. 创建工厂:根据场景选择简单工厂、工厂方法或抽象工厂。
  4. 替换调用点:把所有的 new 替换成工厂调用。
  5. 测试:确保行为不变。

最后,我用一张图来总结工厂模式的核心思想:

工厂模式核心思想 客户端 请求对象 工厂 返回对象 产品接口 具体产品A 具体产品B 具体产品C 客户端不直接 new 具体产品,而是通过工厂获取 工厂封装了创建逻辑,客户端只依赖产品接口 新增产品时,只需添加具体产品类和对应工厂逻辑

工厂模式不是什么高深的技术,它就是一个“封装变化”的实践。把容易变的部分(对象创建)包起来,让稳定的部分(业务逻辑)不受影响。这个思路,其实贯穿了整个设计模式体系。

一句话总结:工厂模式不是让你多写代码,而是让你把代码写在正确的地方。把变化关进笼子里,你的系统才能经得起折腾。

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