工厂模式:从业务痛点到代码落地的创建型方案😎
在日常开发中,我们总会遇到对象创建的场景。比如做一个电商系统,需要创建商品、订单、用户等对象;做一个绘图工具,需要创建圆形、矩形、三角形等图形对象。刚开始可能觉得直接用new关键字创建很简单,但随着业务迭代,问题会慢慢暴露 ------ 当对象创建逻辑变复杂,或者需要频繁新增同类对象时,代码会变得臃肿难维护。这时候,工厂模式就能派上大用场。
一、为什么需要工厂模式?先看个真实痛点
假设我们要开发一个简单的支付系统,初期只支持支付宝支付。代码可能是这样的(以 Java 为例):
typescript
// 支付宝支付类
public class Alipay {
public void pay(double amount) {
System.out.println("使用支付宝支付" + amount + "元");
}
}
// 业务调用处
public class PaymentService {
public void processPayment(String type, double amount) {
// 直接new对象
Alipay alipay = new Alipay();
alipay.pay(amount);
}
}
后来业务扩展,需要新增微信支付。这时候就得修改PaymentService里的代码,新增WechatPay类,再在processPayment里加判断:
typescript
// 新增微信支付类
public class WechatPay {
public void pay(double amount) {
System.out.println("使用微信支付" + amount + "元");
}
}
// 修改业务调用处
public class PaymentService {
public void processPayment(String type, double amount) {
if ("alipay".equals(type)) {
Alipay alipay = new Alipay();
alipay.pay(amount);
} else if ("wechat".equals(type)) { // 新增判断
WechatPay wechatPay = new WechatPay();
wechatPay.pay(amount);
}
}
}
再后来要加银联支付、ApplePay 呢?processPayment里的if-else会越来越长,每次新增支付方式都要修改原有代码 ------ 这既违反了 "开闭原则"(对扩展开放、对修改关闭),也让代码维护成本越来越高。
而工厂模式的核心作用,就是把 "对象创建" 和 "对象使用" 拆分开,让创建逻辑集中在专门的 "工厂" 里,业务代码只需要调用工厂,不用关心对象是怎么造出来的。
二、三种工厂模式:从简单到复杂的演进
工厂模式不是单一的一种模式,而是分了三个层级,对应不同的业务复杂度。我们从简单的开始讲,逐步深入。
1. 简单工厂模式:适用于产品类型较少的场景
简单工厂模式也叫 "静态工厂模式",核心是用一个工厂类,根据传入的参数来创建不同的产品对象。就像一个 "小卖部",告诉老板要什么,老板直接给你对应的商品。
代码落地:重构支付系统
首先定义一个支付接口,统一所有支付方式的行为(这是关键,让不同支付类有共同的标准):
csharp
// 支付接口
public interface Payment {
// 统一支付方法
void pay(double amount);
}
然后实现具体的支付类(支付宝、微信),都实现Payment接口:
java
// 支付宝支付实现
public class Alipay implements Payment {
@Override
public void pay(double amount) {
System.out.println("支付宝支付:" + amount + "元");
}
}
// 微信支付实现
public class WechatPay implements Payment {
@Override
public void pay(double amount) {
System.out.println("微信支付:" + amount + "元");
}
}
接下来创建 "工厂类",把对象创建逻辑集中在这里:
typescript
// 支付工厂(简单工厂)
public class PaymentFactory {
// 静态方法,根据类型创建支付对象
public static Payment createPayment(String type) {
if (type == null) {
return null;
}
// 根据传入的类型,返回对应的支付对象
switch (type.toLowerCase()) {
case "alipay":
return new Alipay();
case "wechat":
return new WechatPay();
default:
throw new IllegalArgumentException("不支持的支付方式:" + type);
}
}
}
最后看业务调用处的变化 ------ 不用再写if-else,直接找工厂要对象:
typescript
// 业务服务类
public class PaymentService {
public void processPayment(String type, double amount) {
// 找工厂创建对象,不用关心创建细节
Payment payment = PaymentFactory.createPayment(type);
payment.pay(amount);
}
}
// 测试代码
public class Test {
public static void main(String[] args) {
PaymentService service = new PaymentService();
service.processPayment("alipay", 199.9); // 输出:支付宝支付:199.9元
service.processPayment("wechat", 299.5); // 输出:微信支付:299.5元
}
}
优缺点很明显
- 优点:业务代码变简洁了,新增支付方式时,只需要新增支付类和修改工厂的switch,不用改业务逻辑。
- 缺点:工厂类会随着产品增多而变大(比如加银联、ApplePay,就要在工厂里加case),还是会违反 "开闭原则"。所以简单工厂适合产品类型少、变动不频繁的场景。
2. 工厂方法模式:解决简单工厂的 "扩展痛点"
如果支付方式会频繁新增(比如每月都要加 1-2 种),简单工厂的switch会越来越长。这时候就需要 "工厂方法模式"------ 它把原来的 "一个工厂" 拆成 "多个工厂",每个产品对应一个专属工厂,新增产品时只需要新增产品类和对应的工厂类,不用改原有代码。
就像把 "小卖部" 改成 "连锁店":买支付宝支付找 "支付宝工厂",买微信支付找 "微信工厂",新增银联支付时,直接开个 "银联工厂" 就行。
代码落地:再重构支付系统
首先,保留之前的Payment接口和具体支付类(Alipay、WechatPay),然后新增 "工厂接口"------ 定义所有工厂的统一行为:
csharp
// 支付工厂接口(所有具体工厂都要实现这个接口)
public interface PaymentFactory {
// 工厂的核心方法:创建支付对象
Payment createPayment();
}
然后为每个支付方式创建专属工厂:
typescript
// 支付宝工厂:只创建支付宝对象
public class AlipayFactory implements PaymentFactory {
@Override
public Payment createPayment() {
return new Alipay();
}
}
// 微信工厂:只创建微信支付对象
public class WechatPayFactory implements PaymentFactory {
@Override
public Payment createPayment() {
return new WechatPay();
}
}
业务调用处也要调整 ------ 现在需要先创建对应的工厂,再用工厂创建支付对象:
java
// 业务服务类
public class PaymentService {
public void processPayment(PaymentFactory factory, double amount) {
// 用工厂创建对象
Payment payment = factory.createPayment();
payment.pay(amount);
}
}
// 测试代码
public class Test {
public static void main(String[] args) {
PaymentService service = new PaymentService();
// 支付宝支付:传支付宝工厂
service.processPayment(new AlipayFactory(), 199.9);
// 微信支付:传微信工厂
service.processPayment(new WechatPayFactory(), 299.5);
}
}
新增产品有多简单?
现在要加 "银联支付",只需要做两步:
- 新增银联支付类:
java
public class UnionPay implements Payment {
@Override
public void pay(double amount) {
System.out.println("银联支付:" + amount + "元");
}
}
- 新增银联工厂类:
typescript
public class UnionPayFactory implements PaymentFactory {
@Override
public Payment createPayment() {
return new UnionPay();
}
}
业务调用时直接传UnionPayFactory就行,不用改任何原有代码 ------ 完全符合 "开闭原则"。
适用场景
产品类型多、且频繁新增的场景,比如电商的商品类型(实物、虚拟、服务)、物流系统的配送方式(快递、自提、同城送)。
3. 抽象工厂模式:处理 "产品族" 场景
有时候我们遇到的不是 "单一产品",而是 "产品族"------ 比如做一个跨平台的 UI 组件库,需要为 Windows 和 Mac 系统分别提供 "按钮" 和 "文本框":Windows 的按钮 + Windows 的文本框是一套,Mac 的按钮 + Mac 的文本框是另一套。这时候工厂方法模式就不够用了,因为它只能创建单一产品,而抽象工厂模式可以创建 "一整套相关产品"。
代码落地:跨平台 UI 组件库
首先定义两个产品接口(按钮、文本框),代表产品族里的不同产品:
csharp
// 按钮接口
public interface Button {
void render(); // 渲染按钮
}
// 文本框接口
public interface TextBox {
void render(); // 渲染文本框
}
然后实现不同平台的具体产品:
typescript
// Windows按钮
public class WindowsButton implements Button {
@Override
public void render() {
System.out.println("渲染Windows风格按钮");
}
}
// Windows文本框
public class WindowsTextBox implements TextBox {
@Override
public void render() {
System.out.println("渲染Windows风格文本框");
}
}
// Mac按钮
public class MacButton implements Button {
@Override
public void render() {
System.out.println("渲染Mac风格按钮");
}
}
// Mac文本框
public class MacTextBox implements TextBox {
@Override
public void render() {
System.out.println("渲染Mac风格文本框");
}
}
接下来定义 "抽象工厂接口"------ 注意:它的方法不是创建单一产品,而是创建产品族里的所有产品:
csharp
// UI组件工厂接口(抽象工厂)
public interface UIFactory {
Button createButton(); // 创建按钮
TextBox createTextBox(); // 创建文本框
}
然后实现不同平台的具体工厂,每个工厂负责创建对应平台的一整套组件:
typescript
// Windows UI工厂:创建Windows全套组件
public class WindowsUIFactory implements UIFactory {
@Override
public Button createButton() {
return new WindowsButton();
}
@Override
public TextBox createTextBox() {
return new WindowsTextBox();
}
}
// Mac UI工厂:创建Mac全套组件
public class MacUIFactory implements UIFactory {
@Override
public Button createButton() {
return new MacButton();
}
@Override
public TextBox createTextBox() {
return new MacTextBox();
}
}
最后看业务调用 ------ 只需要确定用哪个平台的工厂,就能拿到一整套组件:
java
// UI渲染服务
public class UIRenderer {
private Button button;
private TextBox textBox;
// 传入抽象工厂,初始化一整套组件
public UIRenderer(UIFactory factory) {
this.button = factory.createButton();
this.textBox = factory.createTextBox();
}
// 渲染所有组件
public void renderUI() {
button.render();
textBox.render();
}
}
// 测试代码
public class Test {
public static void main(String[] args) {
// Windows平台:用Windows工厂
UIRenderer windowsRenderer = new UIRenderer(new WindowsUIFactory());
windowsRenderer.renderUI();
// 输出:渲染Windows风格按钮 + 渲染Windows风格文本框
// Mac平台:用Mac工厂
UIRenderer macRenderer = new UIRenderer(new MacUIFactory());
macRenderer.renderUI();
// 输出:渲染Mac风格按钮 + 渲染Mac风格文本框
}
}
核心价值
抽象工厂模式的关键是 "产品族" 的概念 ------ 它保证了同一工厂创建的产品是 "配套" 的(比如 Windows 工厂不会造出 Mac 按钮),避免出现 "Windows 按钮 + Mac 文本框" 这种不兼容的组合。
适用场景
需要创建 "一整套相关产品",且产品之间有兼容性要求的场景,比如:跨平台软件(Windows/Mac/Linux)、数据库访问(MySQL/Oracle/PostgreSQL 的连接 + 查询组件)、不同品牌的硬件驱动(华为 / 小米的手机 + 平板驱动)。
三、总结:三种工厂模式怎么选?
很多人学完工厂模式会混淆,其实核心是看业务需求里的 "产品复杂度":
模式类型 | 核心特点 | 适用场景 | 代码复杂度 |
---|---|---|---|
简单工厂模式 | 一个工厂造所有产品 | 产品少、变动少(如简单支付) | 低 |
工厂方法模式 | 一个产品对应一个工厂 | 产品多、频繁新增(如多支付方式) | 中 |
抽象工厂模式 | 一个工厂造一整套产品族 | 需创建配套产品(如跨平台 UI) | 高 |
本质上,三种模式是 "逐步解耦" 的过程:从 "硬编码 new 对象" 到 "集中工厂创建",再到 "按产品 / 产品族拆分工厂",最终目的都是让代码更易维护、易扩展。
在实际开发中,不用一开始就追求最复杂的抽象工厂 ------ 比如做一个小工具,用简单工厂就够了;等业务涨到需要频繁加产品时,再重构为工厂方法模式也不迟。设计模式的核心是 "解决问题",而不是 "炫技"。