设计模式-工厂方法模式

文章目录

  • 一、概述
  • 二、简单工厂
    • [2.1 结构与角色](#2.1 结构与角色)
    • [2.2 代码实现](#2.2 代码实现)
    • [2.3 优缺点分析](#2.3 优缺点分析)
  • 三、工厂方法
    • [3.1 结构与角色](#3.1 结构与角色)
    • [3.2 代码实现](#3.2 代码实现)
    • [3.3 扩展性验证](#3.3 扩展性验证)
    • [3.4 简单工厂 vs 工厂方法](#3.4 简单工厂 vs 工厂方法)
  • [四、JDK 源码中的工厂方法](#四、JDK 源码中的工厂方法)
    • [4.1 Collection.iterator()](#4.1 Collection.iterator())
    • [4.2 java.sql.DriverManager](#4.2 java.sql.DriverManager)
    • [4.3 LoggerFactory](#4.3 LoggerFactory)
  • 五、总结

一、概述

在日常开发中,我们经常会遇到需要创建对象的情况,最常见的方式就是通过 new 关键字来直接创建。但是直接 new 对象会导致代码与具体类强耦合,当需求变化时就需要修改源代码,这显然违背了开闭原则(对扩展开放,对修改关闭)。

那么有没有一种方式,能够让我们在使用对象的时候不需要关心对象是怎么创建的,只需要通过一个统一的入口来获取对象就可以了呢?答案就是------工厂模式

工厂模式是 Java 中最常用的设计模式之一,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。在工厂模式中,我们创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

工厂模式主要分为三种:

  • 简单工厂模式 (Simple Factory):不属于 GoF 23 种设计模式,但它是学习工厂方法模式的基础,又称静态工厂方法模式
  • 工厂方法模式(Factory Method):定义一个创建对象的接口,让子类决定实例化哪个类
  • 抽象工厂模式(Abstract Factory):提供一个创建一系列相关或相互依赖对象的接口

本文先介绍简单工厂和工厂方法,抽象工厂将在下一篇博客中详细讲解。

核心:将对象的创建过程封装在工厂中,客户端通过工厂获取对象,而非直接 new


二、简单工厂

简单工厂模式又叫做静态工厂方法模式,它定义一个工厂类,根据传入的参数决定创建哪一种产品类的实例。

简单工厂不属于 GoF 23 种设计模式,但它简单实用,是理解工厂方法模式的基础。

2.1 结构与角色

简单工厂模式包含以下角色:
创建
创建
创建
实现
实现
Client 客户端
Factory 工厂类
Product 抽象产品
ConcreteProductA 具体产品A
ConcreteProductB 具体产品B

  • Factory(工厂类):简单工厂模式的核心,负责实现创建所有实例的内部逻辑,提供一个静态方法根据传入的参数来创建产品对象
  • Product(抽象产品):所有产品类的父类,描述了所有产品共有的公共接口
  • ConcreteProduct(具体产品):简单工厂模式的创建目标,每个具体产品都实现了抽象产品接口

2.2 代码实现

以咖啡店点咖啡为例,咖啡店提供美式咖啡和拿铁咖啡两种饮品:

(1)抽象产品------咖啡

java 复制代码
/**
 * 抽象产品:咖啡
 */
public abstract class Coffee {

    /**
     * 获取咖啡名称
     */
    public abstract String getName();

    /**
     * 加奶
     */
    public void addMilk() {
        System.out.println("加奶...");
    }

    /**
     * 加糖
     */
    public void addSugar() {
        System.out.println("加糖...");
    }
}

(2)具体产品------美式咖啡

java 复制代码
/**
 * 具体产品:美式咖啡
 */
public class AmericanCoffee extends Coffee {

    @Override
    public String getName() {
        return "美式咖啡";
    }
}

(3)具体产品------拿铁咖啡

java 复制代码
/**
 * 具体产品:拿铁咖啡
 */
public class LatteCoffee extends Coffee {

    @Override
    public String getName() {
        return "拿铁咖啡";
    }
}

(4)工厂类------简单咖啡工厂

java 复制代码
/**
 * 简单工厂:咖啡工厂
 */
public class SimpleCoffeeFactory {

    /**
     * 根据类型创建咖啡对象
     *
     * @param type 咖啡类型
     * @return 咖啡对象
     */
    public static Coffee createCoffee(String type) {
        Coffee coffee = null;
        if ("american".equals(type)) {
            coffee = new AmericanCoffee();
        } else if ("latte".equals(type)) {
            coffee = new LatteCoffee();
        } else {
            throw new RuntimeException("抱歉,本店没有该咖啡");
        }
        return coffee;
    }
}

(5)客户端调用

java 复制代码
public class SimpleFactoryDemo {
    public static void main(String[] args) {
        // 点一杯美式咖啡
        Coffee american = SimpleCoffeeFactory.createCoffee("american");
        System.out.println(american.getName()); // 美式咖啡
        american.addMilk();
        american.addSugar();

        // 点一杯拿铁咖啡
        Coffee latte = SimpleCoffeeFactory.createCoffee("latte");
        System.out.println(latte.getName()); // 拿铁咖啡
        latte.addMilk();
    }
}

2.3 优缺点分析

优点:

  • 封装了对象的创建过程,客户端不需要知道具体的产品类名,只需要知道参数即可
  • 客户端与具体产品类解耦,通过工厂类来间接创建对象

缺点:

  • 违背了开闭原则,每次新增产品都需要修改工厂类的代码
  • 工厂类职责过重,所有产品都在一个工厂中创建,不易维护
  • 使用了 if-elseswitch-case 来判断产品类型,随着产品增多,代码会变得臃肿

由于简单工厂的这些缺点,它只适合产品种类较少且不常扩展的场景。当需要频繁新增产品时,就应该考虑使用工厂方法模式了。


三、工厂方法

工厂方法模式是对简单工厂的进一步抽象化,其核心思想是定义一个创建对象的接口,但由子类决定要实例化的类是哪一个,让工厂方法将实例化推迟到子类。

3.1 结构与角色

工厂方法模式包含以下角色:
创建
实现
实现
创建
创建
实现
实现
Client 客户端
Creator 抽象工厂
Product 抽象产品
ConcreteCreatorA 具体工厂A
ConcreteCreatorB 具体工厂B
ConcreteProductA 具体产品A
ConcreteProductB 具体产品B

  • Creator(抽象工厂):声明了工厂方法,返回一个产品类型的对象,也可以定义一个默认的工厂方法来返回默认的产品对象
  • ConcreteCreator(具体工厂):实现抽象工厂接口,由具体工厂来创建具体的产品对象
  • Product(抽象产品):定义产品的公共接口
  • ConcreteProduct(具体产品):具体的产品实现

与简单工厂的区别在于:工厂方法模式将工厂类抽象化,每个具体产品都对应一个具体工厂,新增产品只需要新增对应的工厂类即可,不需要修改已有代码

3.2 代码实现

继续以咖啡店为例,使用工厂方法模式来重构:

(1)抽象产品------咖啡(与简单工厂相同)

java 复制代码
/**
 * 抽象产品:咖啡
 */
public abstract class Coffee {

    /**
     * 获取咖啡名称
     */
    public abstract String getName();

    /**
     * 加奶
     */
    public void addMilk() {
        System.out.println("加奶...");
    }

    /**
     * 加糖
     */
    public void addSugar() {
        System.out.println("加糖...");
    }
}

(2)具体产品------美式咖啡(与简单工厂相同)

java 复制代码
/**
 * 具体产品:美式咖啡
 */
public class AmericanCoffee extends Coffee {

    @Override
    public String getName() {
        return "美式咖啡";
    }
}

(3)具体产品------拿铁咖啡(与简单工厂相同)

java 复制代码
/**
 * 具体产品:拿铁咖啡
 */
public class LatteCoffee extends Coffee {

    @Override
    public String getName() {
        return "拿铁咖啡";
    }
}

(4)抽象工厂------咖啡工厂

java 复制代码
/**
 * 抽象工厂:咖啡工厂
 */
public interface CoffeeFactory {

    /**
     * 创建咖啡对象
     *
     * @return 咖啡对象
     */
    Coffee createCoffee();
}

(5)具体工厂------美式咖啡工厂

java 复制代码
/**
 * 具体工厂:美式咖啡工厂
 */
public class AmericanCoffeeFactory implements CoffeeFactory {

    @Override
    public Coffee createCoffee() {
        return new AmericanCoffee();
    }
}

(6)具体工厂------拿铁咖啡工厂

java 复制代码
/**
 * 具体工厂:拿铁咖啡工厂
 */
public class LatteCoffeeFactory implements CoffeeFactory {

    @Override
    public Coffee createCoffee() {
        return new LatteCoffee();
    }
}

(7)客户端调用

java 复制代码
public class FactoryMethodDemo {
    public static void main(String[] args) {
        // 创建美式咖啡工厂
        CoffeeFactory americanFactory = new AmericanCoffeeFactory();
        Coffee american = americanFactory.createCoffee();
        System.out.println(american.getName()); // 美式咖啡
        american.addMilk();
        american.addSugar();

        // 创建拿铁咖啡工厂
        CoffeeFactory latteFactory = new LatteCoffeeFactory();
        Coffee latte = latteFactory.createCoffee();
        System.out.println(latte.getName()); // 拿铁咖啡
        latte.addMilk();
    }
}

3.3 扩展性验证

假如咖啡店需要新增一种卡布奇诺咖啡,使用工厂方法模式只需要新增两个类即可,无需修改已有的代码

java 复制代码
/**
 * 具体产品:卡布奇诺
 */
public class CappuccinoCoffee extends Coffee {

    @Override
    public String getName() {
        return "卡布奇诺";
    }
}
java 复制代码
/**
 * 具体工厂:卡布奇诺工厂
 */
public class CappuccinoCoffeeFactory implements CoffeeFactory {

    @Override
    public Coffee createCoffee() {
        return new CappuccinoCoffee();
    }
}

客户端使用:

java 复制代码
// 新增卡布奇诺,只需要创建对应的工厂即可,无需修改已有代码
CoffeeFactory cappuccinoFactory = new CappuccinoCoffeeFactory();
Coffee cappuccino = cappuccinoFactory.createCoffee();
System.out.println(cappuccino.getName()); // 卡布奇诺

对比简单工厂,如果要新增卡布奇诺,就需要修改 SimpleCoffeeFactorycreateCoffee() 方法,增加一个 else if 分支,这显然违背了开闭原则。而工厂方法模式完美地解决了这个问题。

3.4 简单工厂 vs 工厂方法

对比维度 简单工厂 工厂方法
工厂个数 1 个 多个
开闭原则 不符合(需修改工厂类) 符合(新增工厂类即可)
产品创建方式 通过参数判断 通过具体工厂创建
代码复杂度 简单 稍复杂
扩展性
适用场景 产品少且不常扩展 产品多且需频繁扩展

选型建议:

  • 如果产品种类较少且相对稳定,使用简单工厂即可,代码简洁明了
  • 如果产品种类较多、需要频繁扩展,使用工厂方法模式,遵循开闭原则

四、JDK 源码中的工厂方法

工厂方法模式在 JDK 中有着广泛的应用,下面来看几个经典的例子。

4.1 Collection.iterator()

java.util.Collection 接口中的 iterator() 方法就是一个典型的工厂方法。

Collection 是抽象工厂(接口),它声明了工厂方法 iterator(),而 ArrayListLinkedList、HashSet等具体的集合类就是具体工厂,它们各自实现了iterator()` 方法来创建属于自己的迭代器对象。

源码如下:

java 复制代码
// 抽象工厂 ------ Collection 接口声明工厂方法
public interface Collection<E> extends Iterable<E> {
    // 工厂方法:返回一个 Iterator 对象
    Iterator<E> iterator();
    ...
}
java 复制代码
// 具体工厂 ------ ArrayList 实现 iterator() 方法
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {

    // 工厂方法的具体实现:创建 ArrayList 自己的迭代器
    public Iterator<E> iterator() {
        return new Itr();
    }

    // 具体产品:ArrayList 的内部迭代器
    private class Itr implements Iterator<E> {
        int cursor;       // 下一个要返回的元素的索引
        int lastRet = -1; // 上一个返回的元素的索引
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
        ...
    }
}
java 复制代码
// 具体工厂 ------ LinkedList 实现 iterator() 方法
public class LinkedList<E> extends AbstractSequentialList<E>
        implements List<E>, Deque<E>, Cloneable, java.io.Serializable {

    // 工厂方法的具体实现:创建 LinkedList 自己的迭代器
    public Iterator<E> iterator() {
        return listIterator();
    }

    public ListIterator<E> listIterator(int index) {
        checkPositionIndex(index);
        return new ListItr(index);
    }

    // 具体产品:LinkedList 的内部迭代器
    private class ListItr implements ListIterator<E> {
        private Node<E> lastReturned;
        private Node<E> next;
        private int nextIndex;
        private int expectedModCount = modCount;
        ...
    }
}

从上面的源码可以看出,Collection 接口定义了工厂方法 iterator(),而不同的集合类各自实现了该方法,返回不同类型的迭代器。客户端只需调用 iterator() 方法就能获取迭代器,不需要关心迭代器是如何创建的。

4.2 java.sql.DriverManager

java.sql.DriverManager 是 JDBC 中获取数据库连接的核心类,它的 getConnection() 方法也是一种工厂方法的应用。

java 复制代码
// DriverManager 获取连接(简化版)
public class DriverManager {

    /**
     * 根据URL获取数据库连接
     */
    @CallerSensitive
    public static Connection getConnection(String url,
            String user, String password) throws SQLException {
        java.util.Properties info = new java.util.Properties();
        ...
        return getConnection(url, info, Reflection.getCallerClass());
    }

    private static Connection getConnection(String url, java.util.Properties info,
            Class<?> caller) throws SQLException {
        ...
        // 遍历注册的 Driver,调用其 connect 方法创建 Connection
        for (DriverInfo aDriver : registeredDrivers) {
            if (isDriverAllowed(aDriver.driver, callerCL)) {
                Connection con = aDriver.driver.connect(url, info);
                if (con != null) {
                    return con;
                }
            }
        }
        ...
    }
}

不同的数据库驱动(MySQL、Oracle、PostgreSQL 等)实现了 java.sql.Driver 接口,各自的 connect() 方法负责创建对应数据库的 Connection 对象,DriverManager 作为客户端不需要关心具体是哪种数据库连接。

4.3 LoggerFactory

在日志框架 SLF4J 中,LoggerFactory.getLogger() 方法也是一种工厂方法的应用:

java 复制代码
public final class LoggerFactory {

    /**
     * 根据类名获取 Logger 对象
     */
    public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    }

    /**
     * 根据类获取 Logger 对象
     */
    public static Logger getLogger(Class<?> clazz) {
        Logger logger = getLogger(clazz.getName());
        return logger;
    }
}

ILoggerFactory 是抽象工厂接口,不同的日志实现(Logback、Log4j 等)提供各自的 ILoggerFactory 实现,用于创建对应的 Logger 对象。


五、总结

工厂方法模式的核心思想是将对象的创建延迟到子类中实现,让子类决定实例化哪个类,从而实现客户端与具体产品类的解耦。

优点:

  • 遵循开闭原则:新增产品只需要新增对应的工厂类,无需修改已有代码
  • 遵循单一职责原则:每个具体工厂只负责创建对应的产品
  • 客户端不需要知道具体产品类名,只需要知道对应的工厂即可
  • 基于多态性,父类引用指向子类对象,提高代码的可扩展性和可维护性

缺点:

  • 每新增一个产品,就需要新增一个产品类和对应的工厂类,类的个数成对增加,增加了系统的复杂度
  • 增加了系统的抽象性和理解难度

适用场景:

  • 客户端不需要知道具体产品的类名,只需要知道对应的工厂
  • 将对象的创建逻辑从客户端中剥离出来
  • 需要频繁扩展产品族,且希望遵循开闭原则
  • 框架设计中将创建对象的职责委托给子类

简单工厂 vs 工厂方法:简单工厂通过参数决定创建哪种产品,适合产品少且稳定的场景;工厂方法通过子类工厂决定创建哪种产品,适合产品多且需频繁扩展的场景。两者不是替代关系,而是根据实际需求选择即可。


参考博客:

工厂模式 | 菜鸟教程:https://www.runoob.com/design-pattern/factory-pattern.html

相关推荐
多加点辣也没关系5 小时前
设计模式-建造者模式
设计模式·建造者模式
多加点辣也没关系6 小时前
设计模式-桥接模式
设计模式·桥接模式
雪度娃娃8 小时前
结构型设计模式——装饰模式
设计模式·装饰器模式
sensen_kiss8 小时前
CPT304 SoftwareEngineeringII 软件工程 2 Pt.4 设计模式(下)
设计模式·软件工程
多加点辣也没关系9 小时前
设计模式-适配器模式
设计模式
Forget the Dream10 小时前
基于适配器模式的 Axios 封装实践
设计模式·typescript·axios·适配器模式
Java面试题总结11 小时前
【设计模式03】使用模版模式+责任链模式优化实战
设计模式·责任链模式
庞轩px11 小时前
Redis工具类重构——从臃肿到优雅的门面模式实践
数据库·redis·设计模式·重构·门面模式·可扩展性·可维护性
Supersist1 天前
【设计模式03】使用模版模式+责任链模式优化实战
后端·设计模式·代码规范