【设计模式】【创建型模式】工厂方法模式(Factory Methods)

👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD

🔥 2025本人正在沉淀中... 博客更新速度++

👍 欢迎点赞、收藏、关注,跟上我的更新节奏

🎵 当你的天空突然下了大雨,那是我在为你炸乌云

文章目录

一、入门

什么是工厂方法模式?

工厂方法模式(Factory Method Pattern)是一种创建型设计模式,它定义了一个用于创建对象的接口,但由子类决定实例化哪个类。工厂方法模式使类的实例化延迟到子类。

简单工厂方法就是所有的产品都交给一个工厂来处理,而工厂方法是交给多个工厂。

为什么要工厂方法模式?

假设我们正在开发一个支付系统,支持多种支付方式(如支付宝、微信支付、银行卡支付等)。在没有工厂方法模式的情况下,代码可能是这样的:

java 复制代码
// 支付接口
interface Payment {
    void pay(double amount);
}

// 支付宝支付
class Alipay implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("使用支付宝支付: " + amount + " 元");
    }
}

// 微信支付
class WechatPay implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("使用微信支付: " + amount + " 元");
    }
}

// 银行卡支付
class BankCardPay implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("使用银行卡支付: " + amount + " 元");
    }
}

// 客户端代码
public class PaymentSystem {
    public static void main(String[] args) {
        String paymentType = "alipay"; // 假设从配置或用户输入中获取支付方式
        Payment payment = null;

        if (paymentType.equals("alipay")) {
            payment = new Alipay();
        } else if (paymentType.equals("wechatpay")) {
            payment = new WechatPay();
        } else if (paymentType.equals("bankcard")) {
            payment = new BankCardPay();
        } else {
            throw new IllegalArgumentException("不支持的支付方式");
        }

        payment.pay(100.0); // 执行支付
    }
}

问题分析

  1. 代码耦合性高
    • 客户端代码(PaymentSystem)直接依赖具体的支付实现类(如 Alipay、WechatPay 等)。
    • 如果需要新增一种支付方式(如 ApplePay),必须修改客户端代码,增加新的 if-else 分支。
  2. 违反开闭原则
    • 开闭原则(Open/Closed Principle)要求软件实体应对扩展开放,对修改关闭。
    • 在当前实现中,每次新增支付方式都需要修改客户端代码,而不是通过扩展来实现。
  3. 代码重复
    • 如果在多个地方需要创建支付对象,相同的 if-else 逻辑会重复出现,导致代码冗余。
  4. 难以测试
    • 客户端代码与具体支付实现类耦合,难以进行单元测试(例如,无法轻松模拟支付对象)。

如何实现工厂方法模式

主要角色

  1. 抽象产品(Product):定义产品的接口。
  2. 具体产品(Concrete Product):实现抽象产品接口的具体类。
  3. 抽象工厂(Creator):声明工厂方法,返回一个产品对象。
  4. 具体工厂(Concrete Creator):实现工厂方法,返回具体产品的实例。

【案例】支付方式 - 改

抽象产品(Product) :支付方式,Payment类。

java 复制代码
// 支付接口
interface Payment {
    void pay(double amount);
}

具体产品(Concrete Product) : 支付宝支付(Alipay类)、微信支付(WechatPay类)、银行卡支付(BankCardPay类)。

java 复制代码
// 支付宝支付
class Alipay implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("使用支付宝支付: " + amount + " 元");
    }
}

// 微信支付
class WechatPay implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("使用微信支付: " + amount + " 元");
    }
}

// 银行卡支付
class BankCardPay implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("使用银行卡支付: " + amount + " 元");
    }
}

抽象工厂(Creator) : 支付工厂,PaymentFactory类。

java 复制代码
interface PaymentFactory {
    Payment createPayment();
}

具体工厂(Concrete Creator)AlipayFactoryWechatPayFactoryBankCardPayFactory

java 复制代码
// 支付宝支付工厂
class AlipayFactory implements PaymentFactory {
    @Override
    public Payment createPayment() {
        return new Alipay();
    }
}

// 微信支付工厂
class WechatPayFactory implements PaymentFactory {
    @Override
    public Payment createPayment() {
        return new WechatPay();
    }
}

// 银行卡支付工厂
class BankCardPayFactory implements PaymentFactory {
    @Override
    public Payment createPayment() {
        return new BankCardPay();
    }
}

客户端调用

java 复制代码
public class PaymentSystem {
    // 支付方式与工厂类的映射
    private static final Map<String, PaymentFactory> paymentFactories = new HashMap<>();

    static {
        // 初始化映射关系
        paymentFactories.put("alipay", new AlipayFactory());
        paymentFactories.put("wechatpay", new WechatPayFactory());
        paymentFactories.put("bankcard", new BankCardPayFactory());
    }

    // 获取支付对象
    public static Payment getPayment(String paymentType) {
        PaymentFactory factory = paymentFactories.get(paymentType);
        if (factory == null) {
            throw new IllegalArgumentException("不支持的支付方式: " + paymentType);
        }
        return factory.createPayment();
    }

    public static void main(String[] args) {
        String paymentType = "alipay"; // 假设从配置或用户输入中获取支付方式

        // 获取支付对象并执行支付
        Payment payment = getPayment(paymentType);
        payment.pay(100.0);
    }
}

二、工厂方法模式在框架源码中的运用

Java 集合框架中的工厂方法模式

Java 集合框架中的 Collections 工具类提供了多个静态工厂方法,用于创建不可变集合、同步集合等。
示例:Collections.unmodifiableList()
Collections.unmodifiableList() 是一个工厂方法,用于创建不可修改的列表。

java 复制代码
public class Collections {
    public static <T> List<T> unmodifiableList(List<? extends T> list) {
        return new UnmodifiableList<>(list);
    }

    private static class UnmodifiableList<E> extends UnmodifiableCollection<E>
            implements List<E> {
        // 具体实现...
    }
}
  • 工厂方法:unmodifiableList() 是工厂方法,负责创建 UnmodifiableList 对象。
  • 客户端:客户端通过调用 unmodifiableList() 方法获取不可修改的列表,而无需关心具体的实现类。
java 复制代码
List<String> originalList = new ArrayList<>();
originalList.add("A");
originalList.add("B");

List<String> unmodifiableList = Collections.unmodifiableList(originalList);
// unmodifiableList.add("C"); // 抛出 UnsupportedOperationException
  • 解耦:客户端不需要知道 UnmodifiableList 的具体实现,只需要通过工厂方法获取对象。
  • 安全性:工厂方法返回的对象是不可修改的,保证了数据的安全性。

Spring 框架中的工厂方法模式

Spring 框架广泛使用工厂方法模式来创建和管理 Bean 对象。Spring 的 BeanFactoryApplicationContext 是典型的工厂方法模式的实现。
示例:BeanFactory

BeanFactory 是 Spring 的核心接口,负责创建和管理 Bean 对象。它定义了工厂方法 getBean(),用于获取 Bean 实例。

java 复制代码
public interface BeanFactory {
    Object getBean(String name) throws BeansException;
    <T> T getBean(String name, Class<T> requiredType) throws BeansException;
    // 其他方法...
}
  • 工厂方法getBean() 是工厂方法,负责根据名称或类型创建并返回 Bean 对象。
  • 具体工厂 :Spring 提供了多个 BeanFactory 的实现类,例如 DefaultListableBeanFactory
  • 客户端 :Spring 容器(如 ApplicationContext)通过调用 getBean() 方法来获取 Bean 对象。
java 复制代码
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MyService myService = context.getBean("myService", MyService.class);
myService.doSomething();
  • 解耦 :客户端代码(如 MyService)不需要知道具体的 Bean 创建逻辑,只需要通过 getBean() 方法获取对象。
  • 灵活性:Spring 可以通过配置文件或注解动态决定创建哪个 Bean 对象。

三、总结

工厂方法模式的优点

  1. 解耦
    • 工厂方法模式将对象的创建与使用分离,客户端代码只需要依赖抽象的工厂接口和产品接口,而不需要关心具体的实现类。
    • 例如,在 Spring 框架中,客户端通过 BeanFactory 获取 Bean 对象,而不需要知道具体的 Bean 实现类。
  2. 符合开闭原则
    • 当需要新增一种产品时,只需新增一个具体工厂类和具体产品类,而无需修改现有代码。
    • 例如,在支付系统中,新增一种支付方式(如 ApplePay)时,只需新增 ApplePayFactoryApplePay 类,而不需要修改客户端代码。
  3. 可扩展性
    • 工厂方法模式支持动态扩展,可以通过配置文件、依赖注入等方式动态决定使用哪个具体工厂类。
    • 例如,在 JDBC 中,可以通过配置文件动态切换数据库驱动。
  4. 代码复用
    • 工厂类的逻辑可以复用在多个地方,避免了重复的创建逻辑。
    • 例如,在 Log4j 中,Logger.getLogger() 方法可以在多个地方复用,而不需要重复编写日志记录器的创建逻辑。
  5. 易于测试
    • 客户端代码依赖于抽象的工厂接口和产品接口,可以通过模拟工厂类来测试客户端代码,而不需要依赖具体的实现类。

工厂方法模式的缺点

  1. 类的数量增加
    • 每新增一种产品,就需要新增一个具体工厂类和具体产品类,导致类的数量增加,系统复杂度提高。
    • 例如,在支付系统中,如果有 10 种支付方式,就需要 10 个具体工厂类和 10 个具体产品类。
  2. 增加了系统抽象性
    • 工厂方法模式引入了抽象的工厂接口和产品接口,增加了系统的抽象性,可能会使代码更难理解。
    • 例如,对于初学者来说,理解 Spring 的 BeanFactoryApplicationContext 可能需要一定的学习成本。
  3. 不适合简单场景
    • 如果对象的创建逻辑非常简单,使用工厂方法模式可能会显得过于复杂,增加不必要的代码量。
    • 例如,如果只需要创建一个简单的对象,直接使用 new 关键字可能更合适。

工厂方法模式的适用场景

  1. 需要动态创建对象
    • 当对象的创建逻辑需要根据运行时条件动态决定时,工厂方法模式非常适用。
    • 例如,在支付系统中,根据用户选择的支付方式动态创建支付对象。
  2. 需要解耦对象的创建与使用
    • 当客户端代码不需要关心具体对象的创建逻辑时,可以使用工厂方法模式将对象的创建过程封装起来。
    • 例如,在 Spring 框架中,客户端通过 BeanFactory 获取 Bean 对象,而不需要知道具体的 Bean 实现类。
  3. 需要支持多种产品类型
    • 当系统需要支持多种产品类型,并且这些产品有共同的接口时,可以使用工厂方法模式。
    • 例如,在日志框架中,支持多种日志实现(如文件日志、控制台日志等)。
  4. 需要符合开闭原则
    • 当系统需要频繁扩展新的产品类型,并且希望在不修改现有代码的情况下实现扩展时,可以使用工厂方法模式。
    • 例如,在 GUI 库中,支持新增一种控件类型(如按钮、文本框等)。
  5. 需要集中管理对象的创建逻辑
    • 当对象的创建逻辑比较复杂,或者需要在多个地方复用时,可以使用工厂方法模式将创建逻辑集中管理。
    • 例如,在 JDBC 中,通过 DriverManager.getConnection() 集中管理数据库连接的创建逻辑。
相关推荐
考虑考虑1 小时前
JDK9中的dropWhile
java·后端·java ee
想躺平的咸鱼干1 小时前
Volatile解决指令重排和单例模式
java·开发语言·单例模式·线程·并发编程
hqxstudying1 小时前
java依赖注入方法
java·spring·log4j·ioc·依赖
·云扬·2 小时前
【Java源码阅读系列37】深度解读Java BufferedReader 源码
java·开发语言
Bug退退退1233 小时前
RabbitMQ 高级特性之重试机制
java·分布式·spring·rabbitmq
小皮侠3 小时前
nginx的使用
java·运维·服务器·前端·git·nginx·github
Zz_waiting.3 小时前
Javaweb - 10.4 ServletConfig 和 ServletContext
java·开发语言·前端·servlet·servletconfig·servletcontext·域对象
全栈凯哥3 小时前
02.SpringBoot常用Utils工具类详解
java·spring boot·后端
兮动人3 小时前
获取终端外网IP地址
java·网络·网络协议·tcp/ip·获取终端外网ip地址
呆呆的小鳄鱼3 小时前
cin,cin.get()等异同点[面试题系列]
java·算法·面试