第 2 天:工厂方法模式(Factory Method Pattern)—— 创建型模式

第 2 天:工厂方法模式(Factory Method Pattern)------ 创建型模式

1. 核心定义

工厂方法模式定义一个创建对象的接口 (抽象工厂),但由子类决定实例化哪个类。它将对象的创建逻辑延迟到子类中,实现了 "创建责任" 与 "使用责任" 的彻底分离。

简单来说:父类只规定 "要创建什么类型的对象",具体创建 "哪个具体对象" 交给子类来完成。

2. 解决的核心问题

在学习工厂方法前,我们先理解它要解决的痛点 ------简单工厂模式的局限性

  • 简单工厂模式通过一个 "万能工厂类" 创建所有对象,若新增产品(如从 "手机" 新增 "平板"),必须修改工厂类的逻辑(添加 if-elseswitch),违反开闭原则(对扩展开放、对修改关闭);
  • 工厂类职责过重,所有产品的创建逻辑集中在一个类中,代码臃肿且不易维护。

工厂方法模式的解决方案:

  • 将 "万能工厂" 拆分为 "抽象工厂 + 多个具体工厂",每个具体工厂只负责创建一种具体产品;
  • 新增产品时,只需新增 "具体产品类 + 对应的具体工厂类",无需修改原有代码,完全符合开闭原则。

3. 核心角色(4 个)

工厂方法模式的结构清晰,包含 4 个固定角色,缺一不可:

角色名称 核心职责 示例(以 "电子设备生产" 为例)
抽象产品(Product) 定义所有具体产品的公共接口 / 抽象类,规范产品的功能 抽象类 ElectronicDevice,包含抽象方法 powerOn()(开机)
具体产品(Concrete Product) 实现抽象产品的接口,是工厂方法模式的创建目标 Phone(手机)、Tablet(平板),均实现 powerOn()
抽象工厂(Factory) 定义创建具体产品的接口(含抽象方法 createProduct()),声明返回抽象产品类型 接口 DeviceFactory,含方法 createElectronicDevice()
具体工厂(Concrete Factory) 实现抽象工厂的接口,重写 createProduct() 方法,返回具体产品实例 PhoneFactory(创建 Phone)、TabletFactory(创建 Tablet

4. 实现步骤与代码示例(Java)

以 "电子设备生产" 为例,完整实现工厂方法模式:

步骤 1:定义抽象产品(ElectronicDevice)

规范所有电子设备的公共行为(如开机):

java 复制代码
// 抽象产品:电子设备
public abstract class ElectronicDevice {
    // 抽象方法:开机
    public abstract void powerOn();
}

步骤 2:实现具体产品(Phone、Tablet)

每个具体产品对应一种实际设备,实现抽象方法:

java 复制代码
// 具体产品1:手机
public class Phone extends ElectronicDevice {
    @Override
    public void powerOn() {
        System.out.println("手机开机:显示品牌Logo,进入主屏幕");
    }
}

// 具体产品2:平板
public class Tablet extends ElectronicDevice {
    @Override
    public void powerOn() {
        System.out.println("平板开机:自动连接WiFi,显示分屏界面");
    }
}

步骤 3:定义抽象工厂(DeviceFactory)

声明创建产品的接口,返回抽象产品类型(依赖抽象而非具体):

csharp 复制代码
// 抽象工厂:电子设备工厂
public interface DeviceFactory {
    // 抽象方法:创建电子设备(返回抽象产品类型)
    ElectronicDevice createElectronicDevice();
}

步骤 4:实现具体工厂(PhoneFactory、TabletFactory)

每个具体工厂对应一种具体产品,负责创建实例:

java 复制代码
// 具体工厂1:手机工厂
public class PhoneFactory implements DeviceFactory {
    @Override
    public ElectronicDevice createElectronicDevice() {
        // 只负责创建Phone实例
        return new Phone();
    }
}

// 具体工厂2:平板工厂
public class TabletFactory implements DeviceFactory {
    @Override
    public ElectronicDevice createElectronicDevice() {
        // 只负责创建Tablet实例
        return new Tablet();
    }
}

步骤 5:客户端使用(创建与使用分离)

客户端只需依赖 "抽象工厂" 和 "抽象产品",无需知道具体实现,实现解耦:

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 1. 选择具体工厂(如需要手机,就用PhoneFactory)
        DeviceFactory factory = new PhoneFactory(); 
        // DeviceFactory factory = new TabletFactory(); // 切换为平板工厂,只需改这一行
        
        // 2. 通过工厂创建产品(返回抽象产品类型,无需关注具体是Phone还是Tablet)
        ElectronicDevice device = factory.createElectronicDevice();
        
        // 3. 使用产品(调用抽象方法,行为由具体产品决定)
        device.powerOn(); 
    }
}

运行结果(PhoneFactory 时)

plaintext 复制代码
手机开机:显示品牌Logo,进入主屏幕

运行结果(TabletFactory 时)

plaintext 复制代码
平板开机:自动连接WiFi,显示分屏界面

5. 应用场景

当满足以下任一条件时,优先使用工厂方法模式:

  1. 产品种类可能扩展:如系统需支持多种支付方式(微信支付、支付宝支付)、多种日志输出(文件日志、控制台日志),后续可能新增更多类型;
  2. 创建逻辑复杂:如创建对象前需初始化配置、连接资源(如数据库驱动创建),需将复杂逻辑封装在具体工厂中;
  3. 客户端无需关注创建细节 :如框架开发中,用户只需调用 API 获取对象,无需知道对象的创建过程(如 Spring 中的 BeanFactory 就是工厂方法的典型应用)。

经典实战案例

  • Java 中的 Collection 接口:ListSet 是抽象产品,ArrayListHashSet 是具体产品;Collection 的工厂方法(如 Arrays.asList())可视为简化的工厂实现;
  • 日志框架 Logback/Log4j:Logger 是抽象产品,ConsoleAppenderLoggerFileAppenderLogger 是具体产品,LoggerFactory 是抽象工厂的入口。

6. 优缺点分析

优点 缺点
1. 符合开闭原则:新增产品只需加 "具体产品 + 具体工厂",无需改原有代码;2. 解耦创建与使用:客户端只依赖抽象,不依赖具体实现,代码更灵活;3. 单一职责:每个具体工厂只负责创建一种产品,逻辑清晰。 1. 类数量增加:每新增一个产品,需对应新增一个具体工厂,可能导致类膨胀;2. 学习成本:相比简单工厂,需理解 "抽象工厂 + 抽象产品" 的多层结构,对新手不友好。

7. 与简单工厂模式的核心区别

很多人会混淆 "简单工厂" 和 "工厂方法",这里用一句话区分:

  • 简单工厂 :1 个工厂 → 生产 N 种产品(用 if-else 判断),违反开闭原则;
  • 工厂方法:N 个工厂 → 生产 N 种产品(1 个工厂对应 1 种产品),符合开闭原则。

简单工厂是 "工厂方法的简化版",适合产品种类固定、不会频繁扩展的场景(如 JDK 中的 Calendar.getInstance());工厂方法则适合产品种类可能扩展的场景(如业务系统中的支付、登录方式)。

相关推荐
我不是混子2 小时前
什么是MySQL的回表?
后端·mysql
准时睡觉2 小时前
SpringSecurity的使用
java·后端
绝无仅有2 小时前
面试经验之mysql高级问答深度解析
后端·面试·github
fliter2 小时前
12分钟讲解Python核心理念
后端
绝无仅有2 小时前
Java技术复试面试:全面解析
后端·面试·github
用户297994363792 小时前
门户功能技术方案实战:搞定动态更新与高频访问的核心玩法
后端
对不起初见2 小时前
如何在后端优雅地生成并传递动态错误提示?
java·spring boot
tingyu2 小时前
JAXB 版本冲突踩坑记:SPI 项目中的 XML 处理方案升级
java
我不是混子2 小时前
为什么不建议使用SELECT * ?
后端·mysql