Java 中的 Factory Method 和 Abstract Factory
1. 概述
Factory Method 和 Abstract Factory 都属于 creational pattern,都能帮你创建对象而不需要直接 new 具体的类。但这两个模式解决的问题不同,初学者很容易混淆。
在这篇文章中,你将通过 PaymentMethod 例子(Factory Method)和根据主题创建 UI 的系统(Abstract Factory)看到区别。读完之后,你会知道什么时候只需要一个简单的工厂,什么时候需要一个“生成工厂的工厂”。
2. 什么是 Factory Method?
Factory Method 定义了一个方法来创建对象,但由子类决定要创建哪个具体的类。简单来说:你有一个 interface 方法 create(),每个子类重写它以返回不同的实现。
2.1. 需要解决的问题
假设你有一个支付系统,支持多种类型:CreditCardPayment、MomoPayment、BankTransferPayment。如果客户端代码直接 new:
public void processOrder(Order order, String type) {
PaymentMethod payment;
if (type.equals("CREDIT_CARD")) {
payment = new CreditCardPayment();
} else if (type.equals("MOMO")) {
payment = new MomoPayment();
} else {
payment = new BankTransferPayment();
}
payment.pay(order.getAmount());
}
每次添加新的支付类型时,你必须在使用它的每个地方修改这个 if-else。这正是 Factory Method 要解决的代码坏味道。
2.2. 应用 Factory Method
public interface PaymentMethod {
void pay(BigDecimal amount);
}
public class CreditCardPayment implements PaymentMethod {
@Override
public void pay(BigDecimal amount) {
System.out.println("支付 " + amount + " 使用信用卡");
}
}
public class MomoPayment implements PaymentMethod {
@Override
public void pay(BigDecimal amount) {
System.out.println("支付 " + amount + " 通过 Momo");
}
}
说明:PaymentMethod 是公共接口,每个实现只负责自己的支付逻辑。这是工厂将创建的“产品”(product)。
接下来是包含 Factory Method 的 abstract class:
public abstract class PaymentProcessor {
// Factory Method - 子类将决定创建哪个实例
protected abstract PaymentMethod createPaymentMethod();
public void processOrder(Order order) {
PaymentMethod payment = createPaymentMethod();
payment.pay(order.getAmount());
}
}
public class CreditCardProcessor extends PaymentProcessor {
@Override
protected PaymentMethod createPaymentMethod() {
return new CreditCardPayment();
}
}
public class MomoProcessor extends PaymentProcessor {
@Override
protected PaymentMethod createPaymentMethod() {
return new MomoPayment();
}
}
createPaymentMethod()是 Factory Method:它是一个抽象方法,每个子类重写它以返回正确的PaymentMethod类型。processOrder()是共用“模板”,不需要知道正在创建哪种支付类型的细节。
客户端这样使用:
PaymentProcessor processor = new MomoProcessor();
processor.processOrder(new Order(new BigDecimal("150000")));
支付 150000 通过 Momo
2.3. 测试 Factory Method
class PaymentProcessorTest {
@Test
void shouldCreateCorrectPaymentMethod() {
PaymentProcessor processor = new CreditCardProcessor();
PaymentMethod method = processor.createPaymentMethod();
assertThat(method).isInstanceOf(CreditCardPayment.class);
}
}
**注意:**为了测试能调用
createPaymentMethod()(protected),将测试类放在与PaymentProcessor相同的包中,或者如果需要从外部测试,将方法改为public。
3. 什么是 Abstract Factory?
Abstract Factory 提供了一个接口来创建一族(family)相关的对象,而不需要指定每个对象的具体类。与 Factory Method 只创建一种产品不同,Abstract Factory 创建多个必须“一起配套”的产品。
3.1. 需要解决的问题
假设你为应用程序制作 UI 主题:“Light”主题和“Dark”主题。每个主题需要有自己的 Button 和 Checkbox,这两个必须在样式上保持一致 — 不能混合 LightButton 和 DarkCheckbox。
public interface Button {
void render();
}
public interface Checkbox {
void render();
}
public class LightButton implements Button {
@Override
public void render() {
System.out.println("渲染白色背景、黑色文字的按钮");
}
}
public class DarkButton implements Button {
@Override
public void render() {
System.out.println("渲染黑色背景、白色文字的按钮");
}
}
public class LightCheckbox implements Checkbox {
@Override
public void render() {
System.out.println("渲染浅灰色边框的复选框");
}
}
public class DarkCheckbox implements Checkbox {
@Override
public void render() {
System.out.println("渲染白色边框的复选框");
}
}
3.2. 定义 Abstract Factory
public interface UiComponentFactory {
Button createButton();
Checkbox createCheckbox();
}
public class LightThemeFactory implements UiComponentFactory {
@Override
public Button createButton() {
return new LightButton();
}
@Override
public Checkbox createCheckbox() {
return new LightCheckbox();
}
}
public class DarkThemeFactory implements UiComponentFactory {
@Override
public Button createButton() {
return new DarkButton();
}
@Override
public Checkbox createCheckbox() {
return new DarkCheckbox();
}
}
UiComponentFactory 是定义整个产品族(Button + Checkbox)的接口。每个实现(LightThemeFactory、DarkThemeFactory)确保创建的产品在主题上始终一致。
3.3. 在客户端中使用 Abstract Factory
public class ThemedApplication {
private final UiComponentFactory factory;
public ThemedApplication(UiComponentFactory factory) {
this.factory = factory;
}
public void renderUi() {
Button button = factory.createButton();
Checkbox checkbox = factory.createCheckbox();
button.render();
checkbox.render();
}
}
ThemedApplication app = new ThemedApplication(new DarkThemeFactory());
app.renderUi();
渲染黑色背景、白色文字的按钮
渲染白色边框的复选框
ThemedApplication 不知道也不需要知道正在创建哪个具体的类。它只依赖于 UiComponentFactory 接口,所以要更改主题,只需更改注入的工厂即可。
3.4. 测试 Abstract Factory
class ThemedApplicationTest {
@Test
void shouldCreateConsistentComponentsForDarkTheme() {
UiComponentFactory factory = new DarkThemeFactory();
Button button = factory.createButton();
Checkbox checkbox = factory.createCheckbox();
assertThat(button).isInstanceOf(DarkButton.class);
assertThat(checkbox).isInstanceOf(DarkCheckbox.class);
}
}
这个测试确认了 Abstract Factory 最重要的属性:从同一个工厂创建的产品总是属于同一个“族”,不会混合 Light 和 Dark。
4. 比较 Factory Method 和 Abstract Factory
| 标准 | Factory Method | Abstract Factory |
|---|---|---|
| 产品数量 | 1 种产品(PaymentMethod) |
多种相关产品(Button、Checkbox) |
| 机制 | 在子类中重写一个方法 | 将多个工厂方法组合到一个接口中 |
| 关系 | 继承(Inheritance) | 通常使用组合(inject factory) |
| 目的 | 使客户端不必知道一个对象的具体类 | 确保多个相关对象之间的一致性 |
一个简单的记忆方法:Abstract Factory 通常通过一组 Factory Method 来实现。在上面的例子中,UiComponentFactory 中的 createButton() 和 createCheckbox() 正是两个子 Factory Method。
4.1. 何时使用 Factory Method
- 你只需要创建一种对象,但创建逻辑很复杂或根据上下文变化。
- 你在使用类层次结构,想让子类自己决定将使用哪种子对象类型(template method pattern 通常与此模式一起使用)。
4.2. 何时使用 Abstract Factory
- 你需要创建多个相关对象,并且必须确保它们相互兼容(相同主题、相同数据库供应商、相同平台…)。
- 系统可以扩展添加新的“产品族”(例如添加
HighContrastThemeFactory),而不想修改客户端代码。
**注意:**当你只有一种产品时不要使用 Abstract Factory — 它会创建一个冗余的接口层。简单的 Factory Method 就足够了。
5. 与 Spring Boot 集成
在 Spring 中,你很少按照 GoF 的确切结构手写 Factory Method/Abstract Factory,因为 Spring 容器已经充当了工厂的角色。然而,理解这个思想能帮你更好地设计 bean。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
@Configuration
public class PaymentFactoryConfig {
@Bean
public Map<String, PaymentMethod> paymentMethods() {
return Map.of(
"CREDIT_CARD", new CreditCardPayment(),
"MOMO", new MomoPayment()
);
}
}
@Service
public class PaymentService {
private final Map<String, PaymentMethod> paymentMethods;
public PaymentService(Map<String, PaymentMethod> paymentMethods) {
this.paymentMethods = paymentMethods;
}
public void pay(String type, BigDecimal amount) {
PaymentMethod method = paymentMethods.get(type);
if (method == null) {
throw new IllegalArgumentException("不支持的支付类型:" + type);
}
method.pay(amount);
}
}
这里,Spring 容器充当了真正的“工厂”:它预先创建 PaymentMethod bean,客户端只需按键查询。这是 Spring 环境中 Factory Method 的实用变体 — 你不再需要为每种支付类型编写单独的 XxxProcessor 类。
6. 常见错误
- 将 Factory Method 与静态工厂方法混淆(如
Optional.of()、List.of())。静态工厂方法只是比构造函数更简洁的对象创建方式,与 GoF 模式的多态性无关。 - 当只有一个产品时滥用 Abstract Factory:如果你只有
Button而没有随之而来的Checkbox,Abstract Factory 就过度设计了。 - 让工厂知道太多的业务逻辑:工厂应该只负责创建对象,不应该在其中嵌入订单处理、验证等逻辑。
- 忘记确保 Abstract Factory 中的一致性:如果你允许客户端从工厂 A 自己选择
Button,从工厂 B 选择Checkbox,你就破坏了该模式的主要目的。
7. 结论
Factory Method 适合当你需要将一个对象的创建委托给子类时,根据开闭原则帮助代码易于扩展。Abstract Factory 更进一步:它确保一个相关对象的族总是被一致地创建,避免混合不兼容的组件。
在 Spring Boot 的实践中,你通常不会按照 GoF 结构手写,而是利用 DI 容器作为工厂。但清楚理解这两个模式的本质能帮你更好地理解库代码(例如 JDBC 中的 DocumentBuilderFactory、ConnectionFactory)并设计更好的可扩展模块。