工厂方法 (Factory Method)
工厂方法 (Factory Method) 是一种创建型设计模式:父类定义“如何拿到一个对象”的接口,具体由哪个类来造这个对象,交给子类去决定。这样做的目的,是把“用对象”和“造对象”拆开,以后要换一种产品或加一种新产品,不必去改调用方的代码。
问题从哪来?
Section titled “问题从哪来?”学校里经常要导出各种报表:教务处导出成绩单,财务处导出缴费凭证,宿管导出宿舍分配表。如果业务代码里到处直接 new 具体的报表类,会出现两件事:
- 创建逻辑和业务逻辑缠在一起:流程里既关心“导出”这件事,又关心“用哪种报表、怎么构造”,一旦要换一种报表(比如从 PDF 改成 Excel),或者多一种报表类型,就要在多个调用点改代码。
- 扩展成本高:每增加一种报表,调用方就要多一层分支或依赖新的具体类,违反开闭原则(对扩展开放、对修改关闭)。
所以需要一种方式:调用方只依赖“会给我一个报表”的抽象,至于这个报表具体是成绩单、缴费凭证还是宿舍表,由另一层(子类)决定。
- 把“创建对象”抽象成一个方法:在父类里定义工厂方法(例如
createReport()),返回抽象产品类型;父类里的其他逻辑只依赖这个抽象产品,不依赖具体类。 - 子类决定实际创建谁:不同子类实现各自的工厂方法,返回不同的具体产品(成绩单、缴费凭证、宿舍分配表等),但都符合同一个产品接口。
- 产品统一抽象:所有具体报表都实现同一套接口(例如
Report),这样创建者和调用方都只依赖抽象,具体类型由工厂子类在运行时决定。
这样,新增一种报表时,只需新增一个具体产品类和一个具体工厂子类,无需改原有调用代码。
classDiagram
class Creator {
<<abstract>>
+factoryMethod()* Product
+someOperation() void
}
class ConcreteCreatorA {
+factoryMethod() Product
}
class ConcreteCreatorB {
+factoryMethod() Product
}
class Product {
<<interface>>
+doSomething()* void
}
class ConcreteProductA {
+doSomething() void
}
class ConcreteProductB {
+doSomething() void
}
Creator <|-- ConcreteCreatorA
Creator <|-- ConcreteCreatorB
Creator ..> Product : creates
Product <|.. ConcreteProductA
Product <|.. ConcreteProductB
ConcreteCreatorA ..> ConcreteProductA : creates
ConcreteCreatorB ..> ConcreteProductB : creates
Java 示例:校内报表导出
Section titled “Java 示例:校内报表导出”下面用“不同部门导出不同报表”来演示:产品是报表,工厂是报表导出器,子类分别负责成绩单、缴费凭证。
报表接口与具体报表:
// 抽象产品:报表public interface Report { void generate();}
// 具体产品:成绩单public class TranscriptReport implements Report { @Override public void generate() { System.out.println("生成成绩单报表"); }}
// 具体产品:缴费凭证public class PaymentProofReport implements Report { @Override public void generate() { System.out.println("生成缴费凭证报表"); }}导出器基类与具体导出器:
// 抽象创建者:定义工厂方法public abstract class ReportExporter { public void export() { Report report = createReport(); report.generate(); }
protected abstract Report createReport();}
public class TranscriptExporter extends ReportExporter { @Override protected Report createReport() { return new TranscriptReport(); }}
public class PaymentProofExporter extends ReportExporter { @Override protected Report createReport() { return new PaymentProofReport(); }}调用方只依赖抽象导出器:
public class Client { public static void main(String[] args) { ReportExporter exporter = new TranscriptExporter(); exporter.export(); // 输出: 生成成绩单报表
exporter = new PaymentProofExporter(); exporter.export(); // 输出: 生成缴费凭证报表 }}调用方拿的是 ReportExporter,只调 export(),不关心内部是成绩单还是缴费凭证。要加“宿舍分配表”,只需新增 DormAssignmentReport 和 DormAssignmentExporter,无需改这里的客户端代码。
两种常见用法
Section titled “两种常见用法”- 按配置选工厂:通过配置(类型名、枚举等)在启动或运行时选定具体导出器,适合报表类型相对固定、由配置决定的场景。
- 按策略选工厂:根据运行时条件(用户身份、部门、操作类型等)选择不同的工厂实现,适合同一套流程里需要动态切换导出类型的场景。
工厂方法把“创建哪一种对象”推迟到子类:父类定好工厂方法签名和主流程,子类决定实际 new 哪个具体产品。这样在不动调用方的前提下就能扩展新产品类型,符合开闭原则,同时把创建逻辑和使用逻辑分开,维护和扩展都更清晰。