GoF设计模式——工厂方法模式

前言

为什么需要简单工厂模式?

假设在开发一个通知系统,最初只支持邮件通知:

java 复制代码
Notification n = new EmailNotification();
n.send("欢迎注册");

简单直接,没毛病。后来产品加了短信通知,代码变成了这样:

java 复制代码
// 订单模块
if (type.equals("email")) {
    n = new EmailNotification();
} else if (type.equals("sms")) {
    n = new SmsNotification();
}
n.send("订单已提交");

// 用户模块
if (type.equals("email")) {
    n = new EmailNotification();
} else if (type.equals("sms")) {
    n = new SmsNotification();
}
n.send("注册成功");

// 营销模块
if (type.equals("email")) {
    n = new EmailNotification();
} else if (type.equals("sms")) {
    n = new SmsNotification();
}
n.send("限时优惠来袭");

同一段 if-else 复制粘贴到十个地方。产品说"把默认通知方式改成短信",得满世界找,改漏一个就是线上 bug。这就是选择逻辑散落的痛 ------不是构造有多复杂,而是同样的判断写了十遍,改一处容易,改十处就要命。

很自然的想法:把选择逻辑收拢到一个地方,客户端只告诉工厂"我要什么":

java 复制代码
public class NotificationFactory {
    public static Notification create(String type) {
        if ("email".equals(type)) {
            return new EmailNotification();
        } else if ("sms".equals(type)) {
            return new SmsNotification();
        } else {
            throw new IllegalArgumentException("未知的通知类型: " + type);
        }
    }
}

现在改默认通知方式?改工厂一处就行。问题暂时解决了。✅

为什么需要工厂方法模式?

好景不长,产品要加微信、钉钉、站内信......每新增一种,就得打开工厂往里塞 else if

java 复制代码
public static Notification create(String type) {
    if ("email".equals(type)) {
        return new EmailNotification();
    } else if ("sms".equals(type)) {
        return new SmsNotification();
    } else if ("wechat".equals(type)) {
        return new WechatNotification();
    } else if ("dingtalk".equals(type)) {
        return new DingtalkNotification();
    } else if ("inbox".equals(type)) {
        return new InboxNotification();
    } else {
        throw new IllegalArgumentException("未知的通知类型: " + type);
    }
}

工厂从5行膨胀到几十行,每次改动都有引入 bug 的风险。问题出在哪?简单工厂把"判断创建哪种"和"执行创建"绑死在一个类里 ,新增产品必须改老代码,违反开闭原则。

工厂方法模式的思路:不再用一个工厂搞定所有产品,而是定义抽象工厂接口,让每个具体工厂只负责一种产品:

java 复制代码
public interface NotificationFactory {
    Notification create();
}

public class EmailNotificationFactory implements NotificationFactory {
    public Notification create() { return new EmailNotification(); }
}

public class SmsNotificationFactory implements NotificationFactory {
    public Notification create() { return new SmsNotification(); }
}

概念

工厂方法模式(Factory Method)是一种创建型 设计模式,核心思想是将对象的创建延迟到子类。父类定义创建对象的接口(工厂方法),由子类决定实例化哪个具体类。

简单工厂 vs 工厂方法 :简单工厂通过参数 + if-else 集中创建所有产品,适合产品种类少且稳定的场景;工厂方法通过抽象工厂 + 具体工厂将创建职责分散,每个工厂只管一种产品,符合开闭原则------新增产品不改已有代码。

简单工厂模式

简单工厂不属于 GoF 23 种设计模式,更多的是一种编程习惯。它包含三个角色:

  • 工厂类 :负责创建产品,根据参数通过 if-elseswitch 决定实例化哪个具体产品
  • 抽象产品:定义所有产品必须实现的接口
  • 具体产品:实现抽象产品接口,是工厂真正创建的对象

实现
实现
创建
Factory

  • createProduct(type: String) : Product
    <<interface>>
    Product
  • operation() : void
    ConcreteProductA
  • operation() : void
    ConcreteProductB
  • operation() : void

类关系说明:Factory 依赖 Product(创建产品),ConcreteProductAConcreteProductB 实现 Product 接口。

工厂方法模式

工厂方法模式将工厂职责分散到多个具体工厂中,包含四个角色:

  • 抽象工厂:定义创建产品的接口(工厂方法)
  • 具体工厂:实现抽象工厂,每个具体工厂创建一种具体产品
  • 抽象产品:定义产品的接口,是工厂方法的返回类型
  • 具体产品:实现抽象产品接口,是工厂真正创建的对象

实现
实现
实现
实现
创建
<<interface>>
Factory

  • createProduct() : Product
    ConcreteFactoryA
  • createProduct() : Product
    ConcreteFactoryB
  • createProduct() : Product
    <<interface>>
    Product
  • operation() : void
    ConcreteProductA
  • operation() : void
    ConcreteProductB
  • operation() : void

类关系说明:ConcreteFactoryAConcreteFactoryB 实现 Factory 接口,ConcreteProductAConcreteProductB 实现 Product 接口,Factory 依赖 Product(每个具体工厂创建对应的具体产品)。

实现

简单工厂模式

在一家小餐馆,厨师兼任收银员。顾客说"来份宫保鸡丁",厨师就去炒;说"来份鱼香肉丝",厨师就换一道炒。厨师一个人记着所有菜的做法,菜单上加新菜时,厨师得重新学。

简单工厂的实现思想是用一个工厂类集中管理所有产品的创建 ,客户端通过参数告诉工厂要什么,工厂内部通过 if-else 决定创建哪个产品。

java 复制代码
// 抽象产品:通知接口
public interface Notification {
	public void send(String message);
}

// 具体产品:邮件通知
public class EmailNotification implements Notification {
	@Override
	public void send(String message) {
		System.out.println("发送邮件: " + message);
	}
}

// 具体产品:短信通知
public class SmsNotification implements Notification {
	@Override
	public void send(String message) {
		System.out.println("发送短信: " + message);
	}
}

// 简单工厂:一个工厂负责所有产品
public class NotificationFactory {
	public static Notification createNotification(String type) {
		if ("email".equals(type)) {
			return new EmailNotification();
		} else if ("sms".equals(type)) {
			return new SmsNotification();
		} else {
			throw new IllegalArgumentException("未知的通知类型: " + type);
		}
	}
}

问题 :新增"微信通知"时,必须修改 NotificationFactorycreateNotification 方法,添加新的 if-else 分支。产品越多,这个方法越臃肿,违反开闭原则。

工厂方法模式

小餐馆做大了,开成了连锁品牌。总部定义标准菜单(抽象工厂),每家分店负责自己招牌菜的制作(具体工厂)。新增一道菜时,总部只需要指定哪家分店来做,老分店的流程完全不用改。

工厂方法模式的实现思想是将对象的创建延迟到子类,每个具体工厂只负责创建一种产品,新增产品时只需新增工厂类。

java 复制代码
// 抽象产品:通知接口
public interface Notification {
	public void send(String message);
}

// 具体产品:邮件通知
public class EmailNotification implements Notification {
	@Override
	public void send(String message) {
		System.out.println("发送邮件: " + message);
	}
}

// 具体产品:短信通知
public class SmsNotification implements Notification {
	@Override
	public void send(String message) {
		System.out.println("发送短信: " + message);
	}
}

// 抽象工厂:定义创建产品的接口
public interface NotificationFactory {
	public Notification createNotification();
}

// 具体工厂:邮件通知工厂
public class EmailNotificationFactory implements NotificationFactory {
	@Override
	public Notification createNotification() {
		return new EmailNotification();
	}
}

// 具体工厂:短信通知工厂
public class SmsNotificationFactory implements NotificationFactory {
	@Override
	public Notification createNotification() {
		return new SmsNotification();
	}
}

扩展 :新增"微信通知"只需添加 WechatNotificationWechatNotificationFactory,不修改任何已有代码。

带注册表的工厂方法

连锁品牌进一步发展,每家分店在总部注册自己的招牌菜。顾客报菜名,总部系统自动匹配到对应分店下单,不需要人工去查"这道菜归哪家管"。

带注册表的工厂方法的实现思想是用一个 Map 集中管理所有工厂实例,客户端只需传入类型标识,注册表自动匹配对应的工厂,避免客户端直接依赖具体工厂类。

java 复制代码
// 抽象产品
public interface PayService {
	public void pay(String orderId, double amount);
}

// 具体产品
public class AlipayService implements PayService {
	@Override
	public void pay(String orderId, double amount) {
		System.out.println("支付宝支付: 订单" + orderId + ", 金额" + amount);
	}
}

public class WechatPayService implements PayService {
	@Override
	public void pay(String orderId, double amount) {
		System.out.println("微信支付: 订单" + orderId + ", 金额" + amount);
	}
}

// 抽象工厂
public interface PayServiceFactory {
	public PayService createPayService();
}

// 具体工厂
public class AlipayServiceFactory implements PayServiceFactory {
	@Override
	public PayService createPayService() {
		return new AlipayService();
	}
}

public class WechatPayServiceFactory implements PayServiceFactory {
	@Override
	public PayService createPayService() {
		return new WechatPayService();
	}
}

// 工厂注册表:集中管理工厂实例,客户端不直接依赖具体工厂
public class PayServiceFactoryRegistry {
	private static final Map<String, PayServiceFactory> FACTORY_MAP = new HashMap<>();

	static {
		FACTORY_MAP.put("alipay", new AlipayServiceFactory());
		FACTORY_MAP.put("wechat", new WechatPayServiceFactory());
	}

	public static PayServiceFactory getFactory(String type) {
		PayServiceFactory factory = FACTORY_MAP.get(type);
		if (factory == null) {
			throw new IllegalArgumentException("未知的支付类型: " + type);
		}
		return factory;
	}
}

// 使用:客户端只依赖注册表和抽象接口
public class OrderService {
	public void payOrder(String orderId, String payType) {
		PayServiceFactory factory = PayServiceFactoryRegistry.getFactory(payType);
		PayService payService = factory.createPayService();
		payService.pay(orderId, 99.9);
	}
}

适用场景 :大多数业务项目用这种"注册表 + 工厂方法"就够了,兼顾了简洁性和扩展性,避免了大量 if-else,也不需要为每个产品单独写一个工厂类。

如何选择

对比维度 简单工厂 工厂方法 带注册表的工厂方法
复杂度 低,一个工厂类搞定 高,抽象工厂 + 多个具体工厂 中,一个注册表 + 工厂接口
扩展性 差,新增产品要改工厂类 好,新增产品只加新工厂类 好,新增产品加工厂类并注册
开闭原则 违反 符合 符合
适用场景 产品种类 ≤ 3 且基本不扩展 框架/SDK 设计 大多数业务项目
类数量 多(每个产品一个工厂) 适中

简单记忆:产品少且稳用简单工厂,框架设计用工厂方法,业务项目用注册表工厂。

总结

工厂方法模式的本质是把"创建什么对象"的决定权从编译时推迟到运行时,让系统在不修改已有代码的情况下引入新产品。

什么时候用

  • 产品种类可能扩展,不希望每次新增产品都修改已有代码
  • 客户端不应该知道也不关心具体创建了哪个实现类
  • 框架或 SDK 需要把"实例化哪个类"的决定权交给使用者

什么时候不用

  • 产品种类固定且少(≤ 3 个),简单工厂更直接
  • Spring 项目里直接用 @Autowired + @Qualifier,Spring 容器本身就是最强的工厂
对比维度 简单工厂 工厂方法 带注册表的工厂方法
复杂度
扩展性
开闭原则 违反 符合 符合
类数量 适中
推荐场景 产品少且稳定 框架/SDK 设计 大多数业务项目

简单记忆:简单工厂管"造一个、造得快",工厂方法管"造一个、造得开",注册表工厂管"造一个、造得省"。

练习题目

物流运输系统

题目描述 :一家物流公司拥有不同的运输部门(陆运部、海运部),每个部门负责创建对应的运输工具并执行配送任务。不同运输工具的配送方式各不相同。使用带注册表的工厂方法模式设计该物流运输系统。

输入描述 :输入的第一行是一个整数 N(1 ≤ N ≤ 100),表示配送任务的数量。接下来的 N 行,每行包含一个字符串和一个整数,用空格隔开:字符串表示运输部门类型,取值为 "Road""Sea";整数表示配送货物的重量(单位:吨,1 ≤ 重量 ≤ 1000)。

输出描述:对于每个配送任务,输出两行信息:第一行是运输工具的配送方式,第二行是根据货物重量计算的运费。运费计算规则:卡车(Road)运费 = 重量 × 10 元/吨,轮船(Sea)运费 = 重量 × 5 元/吨。

输入示例

bash 复制代码
3
Road 20
Sea 50
Road 15

输出示例

bash 复制代码
Truck delivery by road
Freight: 200
Ship delivery by sea
Freight: 250
Truck delivery by road
Freight: 150

解题思路 :题目中不同运输部门创建不同运输工具,新增运输方式(如空运)时不应修改已有部门代码。采用"注册表 + 工厂方法"的方式:抽象产品 Transport(定义配送方式和运费计算)、具体产品 TruckShip、抽象工厂 LogisticsFactory(声明工厂方法 createTransport())、具体工厂 RoadLogisticsFactorySeaLogisticsFactory、注册表 LogisticsFactoryRegistry(用 Map 集中管理工厂实例)。客户端只依赖注册表和抽象接口,新增运输方式只需新增工厂和产品并注册,不碰已有代码。

java 复制代码
import java.util.*;

public class Main {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		while (n-- > 0) {
			String type = sc.next();
			int weight = sc.nextInt();
			// 客户端只依赖注册表和抽象接口
			LogisticsFactory factory = LogisticsFactoryRegistry.getFactory(type);
			Transport transport = factory.createTransport();
			System.out.println(transport.deliver());
			System.out.println("Freight: " + transport.calculateFreight(weight));
		}
	}
}

// 抽象产品:运输工具
interface Transport {
	public String deliver();
	public int calculateFreight(int weight);
}

// 具体产品:卡车
class Truck implements Transport {
	@Override
	public String deliver() {
		return "Truck delivery by road";
	}

	@Override
	public int calculateFreight(int weight) {
		return weight * 10;
	}
}

// 具体产品:轮船
class Ship implements Transport {
	@Override
	public String deliver() {
		return "Ship delivery by sea";
	}

	@Override
	public int calculateFreight(int weight) {
		return weight * 5;
	}
}

// 抽象工厂
interface LogisticsFactory {
	public Transport createTransport();
}

// 具体工厂:陆运工厂
class RoadLogisticsFactory implements LogisticsFactory {
	@Override
	public Transport createTransport() {
		return new Truck();
	}
}

// 具体工厂:海运工厂
class SeaLogisticsFactory implements LogisticsFactory {
	@Override
	public Transport createTransport() {
		return new Ship();
	}
}

// 工厂注册表:集中管理工厂实例
class LogisticsFactoryRegistry {
	private static final Map<String, LogisticsFactory> FACTORY_MAP = new HashMap<>();

	static {
		FACTORY_MAP.put("Road", new RoadLogisticsFactory());
		FACTORY_MAP.put("Sea", new SeaLogisticsFactory());
	}

	public static LogisticsFactory getFactory(String type) {
		LogisticsFactory factory = FACTORY_MAP.get(type);
		if (factory == null) {
			throw new IllegalArgumentException("未知的运输部门: " + type);
		}
		return factory;
	}
}

扩展:实际项目中的工厂方法模式

Spring BeanFactory

Spring 的 IoC 容器本质上就是工厂方法模式的终极形态。BeanFactory 是抽象工厂,DefaultListableBeanFactory 是具体工厂。写的 @Autowired 注解,背后就是容器调用工厂方法创建 Bean 并注入。新增一种 Bean 实现时,只需加 @Component 注解,Spring 容器自动识别,完全不碰已有代码。

JDBC DriverManager

DriverManager.getConnection(url) 是典型的工厂方法。不同的数据库驱动(MySQL、PostgreSQL、Oracle)各自实现了 java.sql.Driver 接口的工厂方法。切换数据库只需改 URL 和驱动依赖,业务代码一行不动:

java 复制代码
// MySQL
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/db", "root", "pwd");
// PostgreSQL:只改 URL,代码不变
conn = DriverManager.getConnection("jdbc:postgresql://localhost:5432/db", "pg", "pwd");

SLF4J LoggerFactory

每天在用的 LoggerFactory.getLogger(MyClass.class) 就是工厂方法。ILoggerFactory 是抽象工厂,getLogger() 是工厂方法,底层可以切换 Logback、Log4j2、JDK Logging 等不同实现。业务代码只依赖 SLF4J 接口,日志实现随时可以换。

消息队列消费者工厂

微服务架构中,一个系统可能对接 RabbitMQ、Kafka、RocketMQ 等多种消息中间件。通过工厂方法模式,MessageConsumerFactory 定义创建消费者的接口,每种 MQ 实现自己的具体工厂。从 RabbitMQ 迁移到 Kafka,只需新增工厂类,业务消费逻辑不变。

支付系统

电商系统对接支付宝、微信、银联等多种支付渠道,每种渠道的 SDK 调用方式完全不同。用工厂方法模式,PayServiceFactory 统一创建入口,PayServiceFactoryRegistry 通过类型标识自动匹配工厂。接入 Apple Pay 时,只需新增工厂和产品类并注册到 Registry,不碰任何已有支付代码。

现在可能还用不到这些,但等遇到"新增一种实现但不想改老代码"的场景时,会突然发现------这不就是工厂方法模式吗?

相关推荐
代码羊羊2 小时前
Rust 迭代器完全通俗易懂指南(零基础全覆盖)
java·开发语言·rust
MY_TEUCK9 小时前
【Java 后端】SpringBoot 登录认证与会话跟踪实战(JWT + Filter/Interceptor)
java·开发语言·spring boot
今天长肉了吗9 小时前
银行风控项目踩坑实录:指标跑了6小时,风险评分全挂了
java
随读手机10 小时前
多式联运信息交互平台完整方案(2026版)
java·ai·eclipse·云计算·区块链
许彰午11 小时前
03-二叉树——从递归遍历到非递归实现
java·算法
nj012811 小时前
Spring 循环依赖详解:三级缓存、早期引用、AOP 代理与懒加载
java·spring·缓存
野生技术架构师11 小时前
2026年最全Java面试题及答案汇总(建议收藏,面试前看这篇就够了)
java·开发语言·面试
程序员飞哥11 小时前
重构 AI 思维(一):Prompt Engineering,如何下达不可违抗的指令?
人工智能·后端
一只叫煤球的猫12 小时前
ThreadForge 源码解读一:ThreadScope 如何把并发任务放进清晰边界?
java·面试·开源