跳到主要内容

工厂模式

模式概述

定义

工厂模式是一种创建型设计模式,它提供了一种将对象的实例化过程封装在工厂类中的方式。根据实现方式的不同,工厂模式可以分为简单工厂、工厂方法和抽象工厂三种类型。

在工厂模式中,创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象。

简单工厂模式是工厂方法模式的一种特例,不属于 GOF 的 23 中经典设计模式

通俗理解

想象你去一家披萨店点披萨。你不需要自己去厨房制作披萨,只需要告诉服务员你想要什么口味的披萨(比如海鲜披萨或者水果披萨),服务员会把你的需求转达给厨房(工厂),厨房就会制作相应的披萨。这就是工厂模式的基本思想 - 你只需要告诉工厂你要什么,工厂负责创建对应的产品给你。

为什么要用工厂模式?

假设你正在开发一个应用程序,需要处理不同类型的文件(JSON、XML、YAML等)。如果直接在代码中使用new来创建对象:

// 不好的做法
if (fileType.equals("json")) {
parser = new JsonParser();
} else if (fileType.equals("xml")) {
parser = new XmlParser();
} else if (fileType.equals("yaml")) {
parser = new YamlParser();
}

这样的代码有以下问题:

  1. 创建代码散布在各处,难以维护
  2. 每次添加新的文件类型都要修改代码
  3. 代码重复,违反了DRY原则

使用工厂模式后:

// 好的做法
Parser parser = ParserFactory.createParser(fileType);

生活中的例子

  1. 餐厅点餐

    • 顾客(客户端)不需要知道厨房(工厂)如何制作食物
    • 服务员(工厂接口)接收订单
    • 厨师(具体工厂)负责制作具体的菜品
  2. 汽车制造

    • 4S店(客户端)向工厂订购汽车
    • 汽车制造厂(工厂)负责生产具体型号的汽车
    • 不同型号的汽车由不同的生产线(具体工厂)生产

工厂模式分类与实现

工厂模式的演进

1. 简单工厂(Simple Factory)

简单工厂模式不是一种设计模式,反而更像是一种编程习惯(面向抽象(接口)编程 )。简单工厂模式又叫做静态工厂方法模式(static Factory Method pattern),它是通过静态方法接受不同的参数来返回不同的实例对象。

实现方式:定义一个工厂类,根据传入的参数不同返回不同的实例,被创建的实例具有共同的父类或接口。

适用场景:

  1. 需要创建的对象较少
  2. 客户端不关心对象的创建过程。

简单工厂包含如下角色:

  • 抽象产品:定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品:实现或者继承抽象产品的子类。
  • 提供了创建产品的方法,调用者通过该方法来获取产品。

// 简单工厂实现
public class RuleConfigParserFactory {
// 这个方法就像一个万能工人,根据需求制作不同的产品
public static IRuleConfigParser createParser(String configFormat) {
// 创建解析器对象
IRuleConfigParser parser = null;
// 根据配置文件格式选择对应的解析器
if ("json".equalsIgnoreCase(configFormat)) {
parser = new JsonRuleConfigParser(); // 创建JSON解析器
} else if ("xml".equalsIgnoreCase(configFormat)) {
parser = new XmlRuleConfigParser(); // 创建XML解析器
}
return parser;
}
}

优点:

  • 简单直观,容易理解
  • 适合产品种类较少的情况
  • 封装了创建对象的过程,可以通过参数直接获取对象,把对象的创建和业务逻辑分开,避免了可能会修改客户代码的问题。
  • 如果要修改新产品,直接修改工厂类,不需要在源代码中进行修改,降低了客户端修改代码的可能性,更加容易扩展。

缺点:

  • 如果要添加新的产品,还是需要修改工厂类的代码,违背了开闭原则。
  • 工厂类职责过重,违反单一职责原则

优化版本:使用工厂类缓存

public class RuleConfigParserFactory {
private static final Map<String, IRuleConfigParser> cachedParsers = new HashMap<>();

static {
cachedParsers.put("json", new JsonRuleConfigParser());
cachedParsers.put("xml", new XmlRuleConfigParser());
cachedParsers.put("yaml", new YamlRuleConfigParser());
cachedParsers.put("properties", new PropertiesRuleConfigParser());
}

public static IRuleConfigParser createParser(String configFormat) {
if (configFormat == null || configFormat.isEmpty()) {
return null;
}
return cachedParsers.get(configFormat.toLowerCase());
}
}

2. 工厂方法(Factory Method)

提示

简单工厂模式存在一个明显的缺点,就是在增加新产品的时候,还是需要修改工厂类的代码的,这就违背了开闭原则。

定义一个用于创建对象的接口,让子类决定实例化那个产品类对象。工厂方法使一个产品类的实例化延迟到其工厂的子类。

工厂方法模式的目的很简单,就是 封装对象创建的过程,提升创建对象方法的可复用性。 工厂方法模式的主要角色:

  • 抽象工厂:提供了创建产品的接口,调用者通过它访问具体工厂的工厂方法。
  • 具体工厂:主要是实现抽象工厂中的抽象方法,完成具体产品的创建。
  • 抽象产品: 定义了产品的规范,描述了产品的主要特性和功能。
  • 具体产品:实现了抽象产品角色所定义的接口,由具体的工厂来创建,它同具体工厂之间一一对应。

当对象的创建逻辑比较复杂,不只是简单的new一下就可以,而是要组合其他类对象,做各种初始化操作的时候,推荐使用工厂方法模式。 这种模式更像是一个工厂集团,每个工厂专门负责生产一种产品。

// 工厂接口 - 定义了所有工厂都必须实现的方法
public interface IRuleConfigParserFactory {
IRuleConfigParser createParser(); // 创建解析器的方法
}

// JSON解析器工厂 - 专门负责创建JSON解析器
public class JsonRuleConfigParserFactory implements IRuleConfigParserFactory {
@Override
public IRuleConfigParser createParser() {
return new JsonRuleConfigParser(); // 创建JSON解析器
}
}

public class XmlConfigParserFactory implements IConfigParserFactory {
@Override
public IRuleConfigParser createParser() {
return new XmlRuleConfigParser();
}
}

为什么这样设计更好?

  1. 每个工厂类只负责创建一种产品,职责单一
  2. 添加新产品只需要添加新的工厂类,不需要修改现有代码
  3. 符合开闭原则(对扩展开放,对修改关闭)
// 工厂的工厂
public class ConfigParserFactoryMap {
private static final Map<String, IConfigParserFactory> cachedFactories = new HashMap<>();

static {
cachedFactories.put("json", new JsonConfigParserFactory());
cachedFactories.put("xml", new XmlConfigParserFactory());
}

public static IConfigParserFactory getParserFactory(String type) {
if (type == null || type.isEmpty()) {
return null;
}
return cachedFactories.get(type.toLowerCase());
}
}

优点:

  • 用户只需要知道具体工厂的名字,就可以获取到想要的产品,不需要关注产品的创建过程。
  • 在系统新增加产品的时候,只需要添加具体产品类和对应的具体工厂,不需要对原工厂进行修改,满足了开闭原则。

缺点:

  • 每增加一个产品,就需要一个具体的产品类和对应的工厂类,这样会增加系统的复杂度。

3. 抽象工厂(Abstract Factory)

提示

抽象工厂模式比工厂方法模式的抽象程度更高,在工厂方法模式中每一个具体工厂只需要生产一种具体产品,但是在抽象工厂模式中一个具体工厂可以生产一组相关的具体产品,这样一组产品被称为产品族,产品族中的每一个产品成分都属于某一个继承等级结构。

产品等级结构与产品组

  • 产品等级结构:产品等级结构即产品的继承结构,如一个抽象类是电视机,其子类有海尔电视机、海信电视机、TCL 电视机,则抽象电视机与具体电视机之间构成了一个产品等级结构,抽象电视机是父类,而具体品牌的电视机是其子类。
  • 产品族:在抽象工厂模式中,产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品,如海尔电器工厂生产的海尔电视机、海尔电冰箱、海尔电视机位于电视机产品等级结构中,海尔电冰箱位于电冰箱产品等级结构中。 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。 抽象工厂为创建一组对象提供了解决方案,与工厂方法模式相比,抽象工厂模式中的具体工厂不只是创建一种产品,而是负责创建一个产品族,这种模式就像是一个超级工厂,可以生产多个产品系列。比如,一个工厂不仅可以生产轮胎,还可以生产发动机、方向盘等。

// 抽象产品等级接口
public interface IRuleConfigParser {
void parseRule(String ruleConfig);
}

public interface ISystemConfigParser {
void parseSystem(String systemConfig);
}

// 具体产品实现
public class JsonRuleConfigParser implements IRuleConfigParser {
@Override
public void parseRule(String ruleConfig) {
System.out.println("解析JSON规则配置: " + ruleConfig);
}
}

public class JsonSystemConfigParser implements ISystemConfigParser {
@Override
public void parseSystem(String systemConfig) {
System.out.println("解析JSON系统配置: " + systemConfig);
}
}

public class XmlRuleConfigParser implements IRuleConfigParser {
@Override
public void parseRule(String ruleConfig) {
System.out.println("解析XML规则配置: " + ruleConfig);
}
}

public class XmlSystemConfigParser implements ISystemConfigParser {
@Override
public void parseSystem(String systemConfig) {
System.out.println("解析XML系统配置: " + systemConfig);
}
}

// 抽象工厂接口
public interface IConfigParserFactory {
IRuleConfigParser createRuleParser();
ISystemConfigParser createSystemParser();
}

// 具体工厂实现
public class JsonConfigParserFactory implements IConfigParserFactory {
@Override
public IRuleConfigParser createRuleParser() {
return new JsonRuleConfigParser();
}

@Override
public ISystemConfigParser createSystemParser() {
return new JsonSystemConfigParser();
}
}

public class XmlConfigParserFactory implements IConfigParserFactory {
@Override
public IRuleConfigParser createRuleParser() {
return new XmlRuleConfigParser();
}

@Override
public ISystemConfigParser createSystemParser() {
return new XmlSystemConfigParser();
}
}

从上面的代码实现中可以看出,抽象工厂模式向使用(客户)方隐藏了下列变化:

  • 程序所支持的实例集合(具体工厂)的数目。
  • 当前使用的是集合中的哪一个实例;
  • 在任意时刻被实例化的具体类型;

在理解抽象工厂原理时,要 考虑 如何找到某一类产品的争取共性功能 。

提示

JDBC 的实现就是抽象工厂模式,无论使用什么样的数据库,只要数据库支持 JDBC ,就能对数据库进行读写操作。 还有的示例是日志收集工厂。

优点

  • 对于不同产品系列有比较多共性特征时,可以使用抽象工厂模式,有助于提升组件的复用性。
  • 当需要提升代码的扩展性并降低维护成本时,把对象的创建和使用过程分开,能有效地将代码统一到一个级别上。
  • 解决跨平台带来的兼容性问题。

缺点

  • 增加新的产品等级结构麻烦,需要对原有结构进行较大的修改,甚至需要修改抽象层代码,这显然会代码较大的不变,违背了开闭原则。

工厂模式的应用场景

  1. 封装变化

    • 创建对象的过程可能会随时发生变化
    • 需要屏蔽对象创建的细节
  2. 代码复用

    • 创建对象的代码很复杂
    • 需要在多个地方创建同类对象
  3. 隔离复杂性

    • 将对象的创建和使用分离
    • 降低代码的耦合度
  4. 控制复杂度

    • 当系统中的对象种类较多时
    • 通过工厂模式进行分类管理

如何选择工厂模式?

  1. 使用简单工厂

    • 工厂类负责创建的对象比较少
    • 客户端只需要传入工厂类的参数,对于如何创建对象不关心
  2. 使用工厂方法

    • 客户端需要决定实例化哪一个工厂来实现运行时的动态变化
    • 多个工厂类对应多个产品类,每个工厂创建一种产品
  3. 使用抽象工厂

    • 需要创建的产品族具有相同的约束
    • 产品等级结构稳定,设计完成之后不会向系统中增加新的产品等级结构

工厂模式的注意事项

  1. 性能考虑

    • 使用简单工厂时,考虑对象缓存
    • 工厂方法中,可以使用工厂的工厂来缓存工厂实例
  2. 扩展性设计

    • 尽量使用配置文件而不是硬编码来管理工厂类
    • 考虑使用反射机制来创建对象
  3. 异常处理

    • 处理创建对象过程中的异常
    • 提供合理的默认值或降级策略

实际应用示例

1. 日志记录器示例

假设我们需要实现一个可以将日志输出到不同地方(控制台、文件、数据库)的系统:

// 日志记录器接口
public interface Logger {
void log(String message);
}

// 控制台日志记录器
public class ConsoleLogger implements Logger {
@Override
public void log(String message) {
System.out.println("控制台日志: " + message);
}
}

// 文件日志记录器
public class FileLogger implements Logger {
@Override
public void log(String message) {
System.out.println("文件日志: " + message);
// 实际中这里会写入文件
}
}

// 日志记录器工厂接口
public interface LoggerFactory {
Logger createLogger();
}

// 控制台日志记录器工厂
public class ConsoleLoggerFactory implements LoggerFactory {
@Override
public Logger createLogger() {
return new ConsoleLogger();
}
}

// 使用示例
public class LoggerTest {
public static void main(String[] args) {
LoggerFactory factory = new ConsoleLoggerFactory();
Logger logger = factory.createLogger();
logger.log("这是一条测试日志"); // 输出:控制台日志: 这是一条测试日志
}
}

2. 数据库连接示例

不同数据库(MySQL、Oracle、SQLite)的连接创建:

// 数据库连接接口
public interface DBConnection {
void connect();
}

// MySQL连接实现
public class MySQLConnection implements DBConnection {
@Override
public void connect() {
System.out.println("连接到MySQL数据库");
}
}

// Oracle连接实现
public class OracleConnection implements DBConnection {
@Override
public void connect() {
System.out.println("连接到Oracle数据库");
}
}

// 数据库连接工厂接口
public interface DBConnectionFactory {
DBConnection createConnection();
}

// MySQL连接工厂
public class MySQLConnectionFactory implements DBConnectionFactory {
@Override
public DBConnection createConnection() {
return new MySQLConnection();
}
}

// 使用示例
public class DBTest {
public static void main(String[] args) {
DBConnectionFactory factory = new MySQLConnectionFactory();
DBConnection connection = factory.createConnection();
connection.connect(); // 输出:连接到MySQL数据库
}
}

最佳实践

1. 如何选择合适的工厂模式?

  1. 使用简单工厂

    • 当你的产品很少(2-3种)
    • 不经常需要添加新产品
    • 例如:只需要支持JSON和XML两种格式的解析器
  2. 使用工厂方法

    • 当你需要经常添加新的产品
    • 产品的创建逻辑比较复杂
    • 例如:需要支持多种数据库连接
  3. 使用抽象工厂

    • 当你需要创建一系列相关的产品
    • 这些产品是成套出现的
    • 例如:需要同时创建数据库连接和对应的数据库命令

2. 编码建议

  1. 命名规范

    // 好的命名
    public class MySQLConnectionFactory { ... }
    public class JsonParserFactory { ... }

    // 不好的命名
    public class MySQLFactory { ... } // 不够具体
    public class Factory1 { ... } // 完全不具体
  2. 异常处理

    public class ConfigParserFactory {
    public static IConfigParser createParser(String type) {
    try {
    if (type == null || type.isEmpty()) {
    throw new IllegalArgumentException("配置类型不能为空");
    }
    // 创建解析器
    } catch (Exception e) {
    throw new ParserCreationException("创建解析器失败", e);
    }
    }
    }

高级应用:依赖注入框架实现

与工厂模式的关系

  1. 从工厂模式到DI容器的演进

    • 简单工厂:解决了对象创建的问题
    • 工厂方法:增加了扩展性
    • 抽象工厂:支持产品族的创建
    • DI容器:在工厂模式基础上增加了依赖管理和生命周期控制
  2. 工厂职责的扩展

    // 传统工厂模式
    public class DatabaseFactory {
    public static Database createDatabase(String type) {
    if ("MySQL".equals(type)) {
    return new MySQLDatabase();
    }
    // ...
    }
    }

    // DI容器方式
    @Configuration
    public class DatabaseConfig {
    @Bean
    public Database database(DbProperties props) { // 自动注入依赖
    Database db = DatabaseFactory.create(props.getType());
    db.setProperties(props);
    return db;
    }
    }
  3. 配置方式的演进

    • 工厂模式:通常使用代码配置
    • DI容器:支持多种配置方式
      <beans>
      <bean id="rateLimiter" class="com.example.RateLimiter">
      <constructor-arg ref="redisCounter"/>
      </bean>
      </beans>

DI容器的核心功能

  1. 配置解析

    • 通过配置文件(如XML)声明需要创建的对象
    • 指定对象之间的依赖关系
    • 配置对象的作用域(单例/原型)和初始化参数
  2. 对象创建

    • 使用反射机制动态加载类、创建对象
    • 将所有类对象的创建放到一个工厂类中完成
    • 支持构造函数注入和属性注入
  3. 生命周期管理

    • 管理单例对象和原型对象
    • 支持懒加载
    • 支持初始化和销毁方法的配置

实现要点

  1. 工厂模式的应用

    • 使用工厂方法创建Bean实例
    • 利用抽象工厂管理不同类型的Bean创建
    • 结合简单工厂实现单例Bean的缓存
  2. 核心组件

    • BeanFactory:核心工厂接口,定义了Bean的获取方法
    • ApplicationContext:高级工厂接口,扩展了基础工厂功能
    • BeanDefinition:Bean的配置信息,类似于工厂的产品说明书
  3. 关键实现细节

    • Bean的创建使用工厂模式的变体
    • 单例Bean的管理借鉴了简单工厂的缓存思想
    • 原型Bean的创建类似于工厂方法模式

注意事项

  1. 工厂模式的局限性

    • 传统工厂模式难以处理复杂的依赖关系
    • 工厂方法模式不适合管理对象的完整生命周期
    • 抽象工厂模式难以应对动态的对象创建需求
  2. DI容器的解决方案

    • 通过依赖注入解决复杂依赖问题
    • 提供完整的生命周期管理
    • 支持动态Bean定义和创建
  3. 最佳实践

    • 合理使用工厂模式和DI容器
    • 简单对象直接使用工厂模式
    • 复杂依赖关系使用DI容器