文章目录
- 一、概述
- 二、简单工厂
-
- [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-else或switch-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()); // 卡布奇诺
对比简单工厂,如果要新增卡布奇诺,就需要修改 SimpleCoffeeFactory 的 createCoffee() 方法,增加一个 else if 分支,这显然违背了开闭原则。而工厂方法模式完美地解决了这个问题。
3.4 简单工厂 vs 工厂方法
| 对比维度 | 简单工厂 | 工厂方法 |
|---|---|---|
| 工厂个数 | 1 个 | 多个 |
| 开闭原则 | 不符合(需修改工厂类) | 符合(新增工厂类即可) |
| 产品创建方式 | 通过参数判断 | 通过具体工厂创建 |
| 代码复杂度 | 简单 | 稍复杂 |
| 扩展性 | 差 | 好 |
| 适用场景 | 产品少且不常扩展 | 产品多且需频繁扩展 |
选型建议:
- 如果产品种类较少且相对稳定,使用简单工厂即可,代码简洁明了
- 如果产品种类较多、需要频繁扩展,使用工厂方法模式,遵循开闭原则
四、JDK 源码中的工厂方法
工厂方法模式在 JDK 中有着广泛的应用,下面来看几个经典的例子。
4.1 Collection.iterator()
java.util.Collection 接口中的 iterator() 方法就是一个典型的工厂方法。
Collection 是抽象工厂(接口),它声明了工厂方法 iterator(),而 ArrayList、LinkedList、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