深入浅出设计模式【二、工厂方法模式】
一、工厂方法模式介绍
工厂方法模式,又称虚拟构造函数(Virtual Constructor)或多态性工厂(Polymorphic Factory),是一种非常经典且应用广泛的创建型设计模式。
它的核心思想是将对象的实例化过程延迟到子类中进行。父类(工厂)定义了一个创建对象的接口,但由子类来决定要实例化的具体类是哪一个。这使得父类代码可以在不知道所要创建的具体类的情况下,与这些对象进行交互,从而实现了对扩展开放,对修改关闭的原则。
二、核心概念与意图
-
核心概念:
- 产品 (Product): 需要被创建的对象的抽象或接口。它是所有具体产品类需要实现的父类。
- 具体产品 (Concrete Product): 实现产品接口的具体类,是工厂方法最终要创建的对象。
- 创建者/工厂 (Creator): 声明工厂方法的类/接口。它通常包含一些依赖于产品的核心业务逻辑。
- 具体创建者/具体工厂 (Concrete Creator): 重写或实现工厂方法,返回一个具体产品实例的类。
-
意图:
- 定义一个创建对象的接口,但让子类决定将哪一个类实例化。
- 使一个类的实例化延迟到其子类。
- 解耦客户端代码与具体类的依赖,客户端只依赖于产品的抽象接口和创建者的抽象接口,从而支持系统的扩展。
三、适用场景剖析
工厂方法模式在以下场景中非常有用:
- 无法预知对象确切类型及其依赖关系时: 当一个类无法预知它必须创建的对象的类,或者这个对象的创建依赖于外部配置、用户输入或运行时环境时。
- 希望提升系统的可扩展性时: 当你希望为库或框架的用户提供一种扩展其内部组件(即创建新“产品”)的方法时。框架定义接口和工厂方法,用户提供具体实现。
- 需要将对象创建代码集中管理时: 当对象创建过程复杂(如包含一系列步骤、需要配置等),希望将这部分代码与核心业务逻辑分离,提高代码的可维护性。
- 需要重用现有对象来节省资源时: 在某些场景下,工厂方法可以返回一个已存在的对象(例如来自对象池的对象),而不是总是创建一个新的实例。
四、类图解析
下图清晰地展示了工厂方法模式中各个角色之间的关系:
Creator: 声明工厂方法 (factoryMethod()),该方法返回一个Product类型的对象。Creator也可以包含一些依赖于Product的核心业务逻辑 (someOperation())。ConcreteCreatorA和ConcreteCreatorB: 重写factoryMethod(),返回一个特定的ConcreteProduct实例。Product: 定义产品对象的接口,所有具体产品都必须实现这个接口。ConcreteProductA和ConcreteProductB: 实现Product接口的具体类。
调用流程: 客户端代码调用 ConcreteCreator 的 factoryMethod(),该方法创建并返回一个 ConcreteProduct。客户端通过 Product 接口与返回的对象进行交互,因此它并不知道具体产品的确切类型。
五、各种实现方式及其优缺点
1. 经典实现(基于继承)
即上述UML所描述的方式,通过子类重写父类的抽象工厂方法来实现。
- 优点:
- 符合开闭原则: 添加新的产品类型时,只需添加新的具体产品类和具体工厂类,无需修改现有代码。
- 代码解耦: 客户端代码完全依赖于抽象(
Product和Creator),不与任何具体类耦合。
- 缺点:
- 类的数量爆炸性增长: 每增加一种产品,通常就需要增加一个具体产品类和一个具体工厂类,增加了系统的复杂度。
2. 参数化工厂方法
在工厂方法中传入一个参数(如字符串、枚举),根据参数的不同来创建不同的产品。
public abstract class Creator {
public abstract Product factoryMethod(String type);
public void someOperation() {
Product p = factoryMethod("A"); // 通过参数指定类型
p.doSomething();
}
}
public class ConcreteCreator extends Creator {
@Override
public Product factoryMethod(String type) {
if ("A".equals(type)) {
return new ConcreteProductA();
} else if ("B".equals(type)) {
return new ConcreteProductB();
}
throw new IllegalArgumentException("Unknown product type.");
}
}
- 优点:
- 可以减少具体工厂类的数量。
- 缺点:
- 违反了开闭原则。增加新产品时必须修改工厂方法内的逻辑(如添加新的
if-else分支)。 - 方法可能变得冗长复杂。
- 违反了开闭原则。增加新产品时必须修改工厂方法内的逻辑(如添加新的
3. 使用反射或Lambda表达式(现代Java实现)
利用Java的反射机制或函数式接口进一步简化。
// 使用Supplier函数式接口作为“工厂”
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
public class LambdaFactory {
private static final Map<String, Supplier<Product>> map = new HashMap<>();
static {
map.put("A", ConcreteProductA::new);
map.put("B", ConcreteProductB::new);
}
public static Product createProduct(String type) {
Supplier<Product> supplier = map.get(type);
if (supplier != null) {
return supplier.get();
}
throw new IllegalArgumentException("Unknown product type.");
}
}
// 客户端调用:Product p = LambdaFactory.createProduct("A");
- 优点:
- 非常灵活,易于扩展。注册新产品只需向Map中添加新的键值对。
- 代码简洁。
- 缺点:
- 类型安全性稍差(基于字符串的Key)。
- 反射可能带来性能开销(但通常可忽略不计)和安全性问题。
六、最佳实践
- 遵循依赖倒置原则 (DIP): 客户端应始终依赖于
Product和Creator的抽象,而不是它们的实现。这是该模式的核心价值。 - 与简单工厂区分: 简单工厂(静态工厂方法)不是一个独立的设计模式,它虽然将创建逻辑封装了起来,但不具备工厂方法的“可扩展性”。如果需要扩展性(开闭原则),请使用标准的工厂方法模式。
- 命名规范: 工厂方法的名字通常使用
createXXX(),makeXXX(),newInstance(),getXXX()等,以提高代码的可读性。 - 考虑与模板方法模式结合:
Creator类中的someOperation()方法通常是一个模板方法,它定义了操作的骨架,而将对象创建这一步骤延迟到子类(工厂方法)中实现。 - 避免过度设计: 如果对象创建过程非常简单,且不太可能变化,直接使用
new是更简单明了的选择。不要为了使用模式而使用模式。
七、在开发中的演变和应用
工厂方法模式的思想在现代开发中得到了极大的演变和广泛应用:
- IoC容器和DI框架: Spring框架的核心——IoC容器,就是一个超级工厂。它负责创建、组装和管理所有Bean对象的生命周期。应用上下文 (
ApplicationContext) 就是Creator,Bean定义是“产品规范”,而容器本身是负责实例化的“具体创建者”。这是工厂方法模式在架构层面的极致应用。 - 工具库中的静态工厂方法: Java标准库中的
Collections类提供了如Collections.unmodifiableList()、Collections.singletonList()等方法。这些是静态工厂方法,它们返回特定功能的具体列表实现,但客户端只依赖于List接口。 - ORM框架中的SessionFactory: 在Hibernate中,
SessionFactory是一个重量级的工厂,负责创建Session对象。每个Session可以看作是一个数据库连接/工作单元。SessionFactory是线程安全的,而Session是非线程安全的。
八、真实开发案例(Java语言内部、知名开源框架、工具)
-
Java集合框架 (Java Collections Framework):
java.util.Collections类中的各种静态方法,如synchronizedList(List list),返回一个同步的(线程安全的)列表包装器。这是一个参数化工厂方法的例子。
-
Java XML处理 (JAXP):
javax.xml.parsers.DocumentBuilderFactory是一个抽象类(Creator)。- 调用其静态方法
DocumentBuilderFactory.newInstance()会根据系统属性或配置文件,加载一个具体的工厂实例(如Apache Xerces或Saxon的实现)。 - 然后再通过具体工厂的
newDocumentBuilder()(工厂方法)来创建一个DocumentBuilder(Product)。
-
日志框架 (Log4j 2 / SLF4J):
org.apache.logging.log4j.LoggerFactory的getLogger(String name)方法。根据Logger的名称(通常是类名),返回一个特定的Logger实例。这背后使用了工厂方法来管理和复用Logger实例。
-
Spring Framework:
- BeanFactory / ApplicationContext: 如前所述,是终极的工厂方法实现。
getBean(String name)是其核心的工厂方法。 - FactoryBean接口: 这是一个特殊的接口。如果一个Bean实现了
FactoryBean,那么Spring容器获取到的不是这个Bean本身,而是它通过getObject()(工厂方法)所返回的对象。这用于创建一些复杂的、无法直接通过默认构造函数创建的对象。
- BeanFactory / ApplicationContext: 如前所述,是终极的工厂方法实现。
九、总结
| 方面 | 总结 |
|---|---|
| 模式类型 | 创建型设计模式 |
| 核心意图 | 定义一个创建对象的接口,但让子类决定实例化哪个类。将实例化延迟到子类。 |
| 关键角色 | 产品 (Product)、具体产品 (ConcreteProduct)、创建者 (Creator)、具体创建者 (ConcreteCreator) |
| 主要优点 | 1. 强大的解耦能力:将客户端与具体产品分离。 2. 符合开闭原则:易于扩展新的产品类型。 3. 符合单一职责原则:将创建逻辑集中管理。 4. 可读性更高:代码意图更明确。 |
| 主要缺点 | 1. 可能引入类的泛滥:随着产品种类增加,具体工厂类会增多,系统复杂度增加。 2. 增加了抽象性:理解和管理层次结构需要更多精力。 |
| 适用场景 | 1. 系统需要高扩展性,未来可能引入新产品。 2. 无法预知所要创建的对象的具体类型。 3. 希望将对象创建代码与使用代码解耦,集中管理创建逻辑。 |
| 关系与对比 | vs. 简单工厂: 简单工厂不具备工厂方法的“可扩展性”。 vs. 抽象工厂: 工厂方法创建一种产品,抽象工厂创建产品族。 vs. 模板方法: 工厂方法常是模板方法模式中的一个步骤。 |
工厂方法模式通过“延迟实例化到子类”这一巧妙的设计,极大地提高了系统的灵活性和可维护性,是框架设计中不可或缺的基石。理解并熟练运用它,是成为一名优秀架构师的必经之路。
本文来自博客园,作者:NeoLshu,转载请注明原文链接:https://www.cnblogs.com/neolshu/p/19120432

浙公网安备 33010602011771号