工厂模式 ------ 提升代码扩展性的利器
今天我们来学习设计模式中应用最广泛、最能体现面向对象设计原则 的模式之一------工厂模式 。
工厂模式,也称为工厂方法模式,是一种创建型设计模式 。它在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。
这种设计模式也是Java开发中最常见的一种模式,它的主要意图是:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
简单说,工厂模式就是为了提供代码结构的扩展性 ,屏蔽每一个功能类中的具体实现逻辑,让外部可以更加简单地只知道调用即可。同时,它也可以帮助我们去掉众多 if-else 带来的复杂逻辑。当然,它也有一些缺点,比如需要实现的类会变多,维护成本增加。但这些问题都可以通过后续的设计模式组合来逐步降低。
接下来,我会带着大家从真实问题出发,一步步分析工厂模式的价值、实现方式、优缺点以及与其他模式的关联。请大家带着下面几个问题边看边思考,最好把案例代码自己敲一遍:
- 为什么使用工厂模式?
- 工厂模式如何实现?
- 工厂模式有哪些优缺点?
- 工厂模式与简单工厂、抽象工厂有什么区别?
- 在Spring等框架中,工厂模式是如何体现的?
一、为什么使用工厂模式?------一个真实案例带你理解
我们先看一个常见场景:假设我们正在开发一个支付系统 ,支持多种支付方式:支付宝、微信支付、银行卡支付等。
在没有使用工厂模式的情况下,我们可能会写出这样的代码:
public class PaymentService {
public void pay(String type, double amount) {
if ("alipay".equals(type)) {
Alipay alipay = new Alipay();
alipay.pay(amount);
} else if ("wechat".equals(type)) {
WechatPay wechat = new WechatPay();
wechat.pay(amount);
} else if ("bankcard".equals(type)) {
BankCardPay bankCard = new BankCardPay();
bankCard.pay(amount);
} else {
throw new UnsupportedOperationException("不支持的支付类型");
}
}
}
这段代码有什么问题?
- 违反开闭原则 :每当增加一种新的支付方式,我们都得修改
PaymentService中的if-else逻辑,每次修改都可能引入bug。 - 代码臃肿 :随着支付方式增多,
if-else会无限膨胀,可读性和维护性急剧下降。 - 耦合度高 :
PaymentService直接依赖了具体的支付类(Alipay、WechatPay),如果要替换实现或增加新功能,改动成本高。
工厂模式如何解决?
我们将创建支付对象的过程从 PaymentService 中抽离出来,交给一个专门的工厂 去管理。PaymentService 只需要知道工厂,不再依赖具体的支付类。这样一来,新增支付方式时,只需要新增一个具体的支付类和一个对应的工厂类(或修改工厂逻辑),PaymentService 完全不需要改动。
下面我们用工厂方法模式来重构这段代码。
二、工厂模式如何实现?------三种典型形式
工厂模式主要分为三种:简单工厂 、工厂方法 、抽象工厂。我们先从最简单的讲起,逐步深入。
1. 简单工厂(非GoF设计模式,但常用)
简单工厂并不属于23种GoF设计模式,但它是工厂模式的基础,很多项目都在用。它的核心是一个工厂类根据传入的参数返回不同的产品实例。
// 抽象出产品接口
public interface Payment {
void pay(double amount);
}
// 具体产品:支付宝支付
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);
}
}
// 简单工厂
public class PaymentFactory {
public static Payment createPayment(String type) {
if ("alipay".equals(type)) {
return new Alipay();
} else if ("wechat".equals(type)) {
return new WechatPay();
} else {
throw new IllegalArgumentException("不支持的支付类型:" + type);
}
}
}
// 使用
public class PaymentService {
public void pay(String type, double amount) {
Payment payment = PaymentFactory.createPayment(type);
payment.pay(amount);
}
}
优点:
- 将对象的创建和使用分离,客户端不需要关心具体创建细节。
- 减少了客户端代码的重复(不用在每个地方都写
new和if-else)。
缺点:
- 工厂类负责所有产品的创建,一旦新增产品,工厂类就需要修改,违反了开闭原则。
- 产品较多时,工厂类会变得臃肿。
2. 工厂方法模式(GoF设计模式)
工厂方法模式将工厂也抽象化:定义一个创建对象的抽象方法,由子类决定实例化哪个具体产品。这样每个具体产品都有对应的具体工厂,新增产品时只需要新增具体工厂,无需修改已有代码。
// 抽象工厂
public abstract class PaymentFactory {
// 工厂方法,延迟到子类实现
public abstract Payment createPayment();
// 业务方法(可选)
public void pay(double amount) {
Payment payment = createPayment();
payment.pay(amount);
}
}
// 具体工厂:支付宝支付工厂
public class AlipayFactory extends PaymentFactory {
@Override
public Payment createPayment() {
return new Alipay();
}
}
// 具体工厂:微信支付工厂
public class WechatPayFactory extends PaymentFactory {
@Override
public Payment createPayment() {
return new WechatPay();
}
}
// 使用
public class PaymentService {
public void pay(PaymentFactory factory, double amount) {
factory.pay(amount);
}
}
// 客户端
PaymentService service = new PaymentService();
service.pay(new AlipayFactory(), 100.0);
service.pay(new WechatPayFactory(), 200.0);
优点:
- 符合开闭原则:新增产品时,只需新增对应的具体工厂类,不需要修改已有代码。
- 单一职责:每个具体工厂只负责创建一种产品。
- 支持扩展:可以在工厂子类中增加额外的初始化逻辑。
缺点:
- 类的数量增加:每增加一个产品,就需要增加一个具体工厂类,导致系统复杂度上升。
- 如果产品类型非常多,代码量会很大。
3. 抽象工厂模式(GoF设计模式)
抽象工厂模式用于创建一组相关或相互依赖的对象,而不是单个对象。比如支付系统不仅需要支付对象,还需要退款对象、对账对象等。抽象工厂会提供一个接口,用于创建这一系列对象。
// 抽象产品A:支付
public interface Payment { ... }
// 抽象产品B:退款
public interface Refund { ... }
// 具体产品族:支付宝系列
public class AlipayPayment implements Payment { ... }
public class AlipayRefund implements Refund { ... }
// 具体产品族:微信系列
public class WechatPayment implements Payment { ... }
public class WechatRefund implements Refund { ... }
// 抽象工厂
public interface PaymentFactory {
Payment createPayment();
Refund createRefund();
}
// 具体工厂:支付宝工厂
public class AlipayFactory implements PaymentFactory {
@Override
public Payment createPayment() {
return new AlipayPayment();
}
@Override
public Refund createRefund() {
return new AlipayRefund();
}
}
// 具体工厂:微信工厂
public class WechatFactory implements PaymentFactory {
@Override
public Payment createPayment() {
return new WechatPayment();
}
@Override
public Refund createRefund() {
return new WechatRefund();
}
}
优点:
- 保证同一产品族的产品一起使用,一致性高。
- 易于替换产品族(只需切换具体工厂)。
- 符合开闭原则,增加新的产品族时不需要修改已有代码。
缺点:
- 增加新产品类型(比如增加"对账"产品)时,需要修改所有工厂接口和具体工厂,扩展性较差。
三、工厂模式有哪些优缺点?
优点
- 解耦:将对象的创建和使用分离,客户端无需知道具体类的名称,只需知道工厂。
- 扩展性好:新增产品时,只需增加对应的工厂类,符合开闭原则(工厂方法模式)。
- 代码复用 :工厂中的创建逻辑可以复用,避免在多个地方重复
new。 - 符合单一职责原则:将创建对象的职责集中到工厂,让客户端专注于业务逻辑。
缺点
- 类数量增加:每增加一个产品,就需增加一个具体工厂类,系统复杂度上升。
- 理解成本高:对于简单场景,引入工厂模式可能会过度设计,增加学习成本。
- 抽象工厂扩展困难:在抽象工厂中增加新产品类型时,需要修改所有工厂类,违反开闭原则。
四、工厂模式与简单工厂、抽象工厂的区别
|----------|----------------------|---------------|-------------------|
| 模式 | 定义 | 适用场景 | 优缺点 |
| 简单工厂 | 一个工厂类根据参数返回不同产品实例 | 产品种类较少,且不频繁增加 | 简单,但违反开闭原则 |
| 工厂方法 | 将工厂抽象,由子类决定实例化哪个具体产品 | 产品种类多,且可能频繁增加 | 符合开闭原则,但类数量多 |
| 抽象工厂 | 创建一组相关产品对象 | 需要保证产品族一致性 | 易于替换产品族,但增加产品类型困难 |
五、在Spring等框架中,工厂模式是如何体现的?
Spring框架大量使用了工厂模式,尤其是BeanFactory 和ApplicationContext,它们就是典型的工厂模式应用。
- BeanFactory:是Spring容器的根接口,定义获取Bean的方法。它根据配置(XML或注解)创建和管理Bean实例,这就是工厂模式的思想。
- ApplicationContext:继承自BeanFactory,提供了更多企业级功能,本质上也是一个工厂。
- FactoryBean接口:允许自定义复杂对象的创建逻辑,是工厂方法模式的体现。
在Spring中,我们通常只需要通过注解或XML定义Bean,容器负责创建和管理,实现了对象创建和使用的完全解耦。
六、工厂模式与依赖注入(DI)的关系
依赖注入是控制反转的一种实现方式,它和工厂模式有异曲同工之妙:都是将对象的创建权从使用方移交给第三方。
- 工厂模式是编程层面的创建者,由开发者编写工厂类决定创建逻辑。
- 依赖注入是容器层面的创建者,由框架(如Spring)负责创建和管理对象,开发者只需声明依赖。
在现代Java开发中,我们更倾向于使用依赖注入(如Spring)来替代手写的工厂模式,因为框架提供了更强大、更灵活的管理能力(如作用域、生命周期回调、AOP等)。但理解工厂模式对于理解Spring原理至关重要。
七、总结
今天我们通过支付系统的案例,看到了工厂模式如何帮助我们去掉 if-else、提升代码扩展性。同时,我们也分析了工厂模式的优缺点,以及与Spring框架的关联。
关键点回顾:
- 工厂模式的核心是将对象的创建和使用分离。
- 工厂方法模式符合开闭原则,但会增加类数量。
- 抽象工厂模式适合创建一组相关对象。
- 在Spring中,BeanFactory和FactoryBean都是工厂模式的体现。