跳转到内容

工厂方法 (Factory Method)

工厂方法 (Factory Method) 是一种创建型设计模式:父类定义“如何拿到一个对象”的接口,具体由哪个类来造这个对象,交给子类去决定。这样做的目的,是把“用对象”和“造对象”拆开,以后要换一种产品或加一种新产品,不必去改调用方的代码。

学校里经常要导出各种报表:教务处导出成绩单,财务处导出缴费凭证,宿管导出宿舍分配表。如果业务代码里到处直接 new 具体的报表类,会出现两件事:

  1. 创建逻辑和业务逻辑缠在一起:流程里既关心“导出”这件事,又关心“用哪种报表、怎么构造”,一旦要换一种报表(比如从 PDF 改成 Excel),或者多一种报表类型,就要在多个调用点改代码。
  2. 扩展成本高:每增加一种报表,调用方就要多一层分支或依赖新的具体类,违反开闭原则(对扩展开放、对修改关闭)。

所以需要一种方式:调用方只依赖“会给我一个报表”的抽象,至于这个报表具体是成绩单、缴费凭证还是宿舍表,由另一层(子类)决定。

  • 把“创建对象”抽象成一个方法:在父类里定义工厂方法(例如 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

下面用“不同部门导出不同报表”来演示:产品是报表,工厂是报表导出器,子类分别负责成绩单、缴费凭证。

报表接口与具体报表:

// 抽象产品:报表
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(),不关心内部是成绩单还是缴费凭证。要加“宿舍分配表”,只需新增 DormAssignmentReportDormAssignmentExporter,无需改这里的客户端代码。

  • 按配置选工厂:通过配置(类型名、枚举等)在启动或运行时选定具体导出器,适合报表类型相对固定、由配置决定的场景。
  • 按策略选工厂:根据运行时条件(用户身份、部门、操作类型等)选择不同的工厂实现,适合同一套流程里需要动态切换导出类型的场景。

工厂方法把“创建哪一种对象”推迟到子类:父类定好工厂方法签名和主流程,子类决定实际 new 哪个具体产品。这样在不动调用方的前提下就能扩展新产品类型,符合开闭原则,同时把创建逻辑和使用逻辑分开,维护和扩展都更清晰。