工厂模式

设计模式笔记:工厂方法模式 (Factory Method Pattern)

一、一句话概括

定义一个用于创建对象的接口(工厂方法),但将 “究竟创建哪种具体对象” 的决定权推迟到子类去实现。

二、为什么需要它 (Why - The Pain Point)

想象一下,你正在开发一个应用,需要根据不同的场景创建不同的对象。比如一个文档处理程序,它可以创建和操作不同类型的文档(Word, PDF, Txt)。

最初的简单实现(糟糕的设计):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class DocumentProcessor {
public Document createDocument(String type) {
Document doc = null;

// 痛点:大量的 if-else 或 switch-case 语句
if ("word".equalsIgnoreCase(type)) {
doc = new WordDocument();
} else if ("pdf".equalsIgnoreCase(type)) {
doc = new PdfDocument();
} else if ("txt".equalsIgnoreCase(type)) {
doc = new TxtDocument();
}
// ...
return doc;
}

// ... 其他处理文档的逻辑
}

这种设计存在严重问题:

  1. 违反开闭原则 (Open/Closed Principle):每当需要支持一种新的文档类型(如 MarkdownDocument),你都必须修改 DocumentProcessor 类的 createDocument 方法,增加一个新的 else if 分支。这使得类变得不稳定、难以维护。
  2. 耦合度高DocumentProcessor 这个高层组件,直接依赖于 WordDocument, PdfDocument 等低层具体类。它知道了太多不该知道的实现细节。
  3. 职责不单一DocumentProcessor 不仅要负责处理文档的通用逻辑,还要负责“如何创建”具体文档的逻辑,职责混杂。

核心问题:如何让一个类在不知道具体要创建哪个子类的情况下完成对象的创建,并将这种不确定性隔离出去,以便系统能灵活地扩展?

三、它是什么 (What - The Solution)

工厂方法模式通过引入一个抽象的创建过程来解决这个问题。它将对象的创建逻辑从使用者(DocumentProcessor)中剥离出来,并封装到一个专门的“工厂”结构中。

核心思想和角色:

  1. **产品接口 (Product)**:定义了工厂方法所创建的对象的共同接口。例如,Document 接口,包含 open(), save() 等方法。
  2. **具体产品 (Concrete Product)**:实现了产品接口的具体类。例如,WordDocument, PdfDocument
  3. **创建者类 (Creator)**:
    • 它通常是一个抽象类或接口。
    • 它声明了一个抽象的**工厂方法 (factoryMethod)**,该方法的返回类型是产品接口(Product)。
    • 它可以包含一些依赖于产品对象的业务逻辑(例如,一个 processDocument() 方法,它会先调用工厂方法创建产品,然后再使用该产品)。
  4. **具体创建者 (Concrete Creator)**:
    • 继承或实现创建者类。
    • 重写(实现) 了工厂方法,返回一个具体的、特定的产品实例。例如,WordDocumentFactory 的工厂方法返回 new WordDocument()

C++ 背景对比 & Java 关键点:

  • 与C++的联系:这与 C++ 中基类定义一个纯虚函数(Pure Virtual Function) 来创建对象,然后由派生类重写该函数以返回具体派生类实例的思想完全一致。这个纯虚函数就是“工厂方法”。
  • Java 的实现精髓
    • 抽象 (abstract classinterface): 是实现该模式的基石,完美体现了 “面向接口编程,而非面向实现编程” 的原则。
    • **依赖倒置原则 (Dependency Inversion Principle)**:高层模块(如客户端代码)不依赖于低层模块(具体产品、具体工厂),而是两者都依赖于抽象(产品接口、创建者接口)。工厂方法是这一原则的经典实践。

四、怎么做 (How - The Blueprint)

基本步骤(以重构文档处理器为例):

  1. 定义产品接口 Document 和具体产品 WordDocument, PdfDocument
  2. **定义抽象创建者 DocumentFactory**。它包含一个抽象方法 createDocument()
  3. 创建具体创建者,如 WordDocumentFactoryPdfDocumentFactory。每个类都重写 createDocument() 方法,返回自己负责的产品实例。
  4. 客户端代码根据需要选择一个具体的工厂来使用。它只与抽象的 DocumentFactoryDocument 交互,完全不知道具体产品的存在。

Java 示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// 1. 产品接口 (Product)
interface Document {
void open();
void save();
}

// 2. 具体产品 (ConcreteProduct)
class WordDocument implements Document {
@Override
public void open() { System.out.println("Opening Word document..."); }
@Override
public void save() { System.out.println("Saving Word document..."); }
}

class PdfDocument implements Document {
@Override
public void open() { System.out.println("Opening PDF document..."); }
@Override
public void save() { System.out.println("Saving PDF document..."); }
}

// 3. 创建者类 (Creator / Abstract Factory)
abstract class DocumentFactory {
// 这是一个通用的业务方法,它不关心具体文档类型
public void manageDocument() {
// 使用工厂方法创建产品
Document doc = this.createDocument();
doc.open();
// ... do some processing
doc.save();
}

// 这就是“工厂方法”,由子类决定如何实现
public abstract Document createDocument();
}

// 4. 具体创建者 (ConcreteCreator)
class WordDocumentFactory extends DocumentFactory {
@Override
public Document createDocument() {
// 返回具体的产品实例
return new WordDocument();
}
}

class PdfDocumentFactory extends DocumentFactory {
@Override
public Document createDocument() {
return new PdfDocument();
}
}

// 客户端代码
public class FactoryMethodDemo {
public static void main(String[] args) {
DocumentFactory factory;

// 场景1:需要处理 Word 文档
System.out.println("--- Client needs to work with Word ---");
factory = new WordDocumentFactory();
factory.manageDocument();

System.out.println();

// 场景2:需要处理 PDF 文档
System.out.println("--- Client needs to work with PDF ---");
factory = new PdfDocumentFactory();
factory.manageDocument();

// 如果未来要支持 Markdown,只需新增 MarkdownDocument 和 MarkdownDocumentFactory
// 无需修改任何现有代码,符合开闭原则。
}
}

五、UML 类图

下面是上述例子的 PlantUML 代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@startuml
skinparam classAttributeIconSize 0
hide empty members

' Product Hierarchy
interface Document {
+ void open()
+ void save()
}
class WordDocument implements Document
class PdfDocument implements Document

' Creator Hierarchy
abstract class DocumentFactory {
' 业务方法
+ void manageDocument()
' 工厂方法
+ {abstract} Document createDocument()
}
class WordDocumentFactory extends DocumentFactory {
+ Document createDocument()
}
class PdfDocumentFactory extends DocumentFactory {
+ Document createDocument()
}

' Relationships
' Creator 依赖于 Product 接口
DocumentFactory ..> Document : uses

' ConcreteCreator 创建 ConcreteProduct
WordDocumentFactory ..> WordDocument : creates
PdfDocumentFactory ..> PdfDocument : creates

' Client
class FactoryMethodDemo {
}
FactoryMethodDemo ..> DocumentFactory : uses

@enduml

图示解读:

  • 顶层是产品 (Document) 和创建者 (DocumentFactory) 的抽象。
  • 底层是平行的两组具体实现:WordDocument 对应 WordDocumentFactoryPdfDocument 对应 PdfDocumentFactory
  • 客户端 (FactoryMethodDemo) 只和顶层的抽象打交道。

六、优缺点分析

优点 (Pros):

  1. 完美的解耦:将产品的创建逻辑从客户端代码中分离出来,客户端只关心产品接口,不关心具体实现。
  2. 符合开闭原则:增加新产品时,只需要增加对应的具体产品类和具体工厂类,无需修改现有代码,系统的可扩展性非常好。
  3. 单一职责原则:创建产品的代码被集中到具体的工厂类中,使得代码结构更清晰,职责更明确。
  4. 灵活性:子类可以自由决定创建哪个具体产品,甚至可以有更复杂的创建逻辑(比如从缓存中获取,或者创建一个配置好的对象)。

缺点 (Cons):

  1. 类的数量增多:每增加一个产品,通常就需要增加一个对应的具体工厂类。这会使得系统中的类的个数成倍增加,增加了系统的复杂度和理解成本。
    • 优化:如果工厂的创建逻辑很简单(就是 new 一下),可以使用反射(Reflection)Lambda 表达式来简化,避免创建大量的具体工厂类。

七、在 Java 中的应用

工厂方法模式在各种框架和库中无处不在,是解耦的利器。

  • java.util.Collection 接口中的 iterator() 方法: Collection 是抽象创建者,iterator() 就是工厂方法,Iterator 是产品接口。每个具体的集合类(ArrayList, LinkedList)都实现了自己的 iterator() 方法,返回一个适合自身数据结构的具体迭代器(ArrayListIterator, LinkedListIterator)。
  • JDBC (Java Database Connectivity): java.sql.DriverManager.getConnection(...) 背后就用了类似工厂方法的思想来根据不同的数据库URL返回不同厂商的 Connection 实现。
  • SLF4J (Simple Logging Facade for Java): LoggerFactory.getLogger(...) 会根据 classpath 下的日志实现(Logback, Log4j2等)来返回一个具体的 Logger 实例。

工厂方法模式是理解更复杂的抽象工厂模式的基础。掌握它,你就掌握了面向对象设计中“延迟决策”和“依赖倒置”的精髓。