深入浅出设计模式【三、抽象工厂模式】
一、抽象工厂模式介绍
抽象工厂模式,又称工具箱(Kit),是一种创建型设计模式。它能创建一系列相关或相互依赖的对象,而无需指定它们具体的类。
该模式提供了一个高层次的接口,用于创建整个产品族(a family of products),而不是单个产品。客户端代码通过这个抽象接口与工厂交互,从而与具体产品的实现解耦。这使得在不修改客户端代码的情况下,切换整个产品族变得非常容易(例如,从一套“现代风格”的UI组件切换到一套“古典风格”的UI组件)。
二、核心概念与意图
-
核心概念:
- 抽象工厂 (Abstract Factory): 声明一个创建一系列抽象产品(属于同一产品族)的操作接口。
- 具体工厂 (Concrete Factory): 实现抽象工厂的接口,负责创建属于特定产品变体族(如“现代”或“古典”)的具体产品对象。
- 抽象产品 (Abstract Product): 为产品族中的每种产品类型声明一个接口(如
Button,TextField)。 - 具体产品 (Concrete Product): 实现抽象产品接口,由具体工厂创建。这些产品组合在一起构成了一个产品族(如
ModernButton,ModernTextField)。 - 客户端 (Client): 只使用由抽象工厂和抽象产品声明的接口。它不知道正在使用的是哪个具体工厂或具体产品。
-
意图:
- 提供一个接口,用于创建相关的或依赖对象的家族,而不需要明确指定具体类。
- 确保一系列相关的产品被一起使用,保持产品之间的一致性。
- 将客户端与具体的产品类解耦,支持产品族和产品类型的轻松切换。
三、适用场景剖析
抽象工厂模式在以下场景中极为有效:
- 系统需要独立于其产品的创建、组合和表示时: 当希望配置系统使用多种产品系列(变体)中的一种时。
- 系统需要由多个相关产品对象来构成一个产品族,并希望保证它们一起使用时: 例如,一个GUI应用需要确保所有UI组件(按钮、文本框、下拉框)都具有同一种视觉风格(如“macOS风格”或“Windows风格”),不能混用。
- 需要提供一个产品类库,但只想暴露它们的接口,而不是实现时: 隐藏具体的产品类和创建过程。
关键区别:
- 工厂方法模式: 关注于创建单一产品,并通过子类来决定实例化哪个具体产品。
- 抽象工厂模式: 关注于创建整个产品族(多个相关产品),通常通过具体工厂类(而不是子类化)来实现。一个具体工厂通常使用多个工厂方法来创建不同类型的产品。
四、类图解析
以下Mermaid类图清晰地展示了抽象工厂模式的结构和角色间的关系:
AbstractFactory: 声明一组创建抽象产品的方法 (createProductA,createProductB)。ConcreteFactory1,ConcreteFactory2: 实现AbstractFactory接口,创建属于特定产品族(如风格1、风格2)的具体产品对象。AbstractProductA,AbstractProductB: 为每种产品类型声明接口。ProductA1,ProductA2,ProductB1,ProductB2: 实现抽象产品接口的具体类。ProductA1和ProductB1属于同一产品族(由ConcreteFactory1创建),ProductA2和ProductB2属于另一产品族(由ConcreteFactory2创建)。- 客户端: 依赖于
AbstractFactory和AbstractProduct接口。它通过AbstractFactory获取产品,并通过AbstractProduct接口使用产品。
五、各种实现方式及其优缺点
1. 经典实现(基于接口/抽象类)
即上述UML所描述的标准方式,为每个产品族定义一个具体的工厂类。
- 优点:
- 极强的产品族一致性保证: 一个具体工厂只创建属于同一族的产品,绝不会出现风格混搭。
- 符合开闭原则: 要引入新的产品族(如“未来风格”),只需添加新的具体工厂类和具体产品类,无需修改现有代码。
- 符合单一职责原则: 将产品创建逻辑集中到工厂类中。
- 符合依赖倒置原则: 客户端代码只依赖于抽象。
- 缺点:
- 难以支持“新种类产品”: 这是最大的缺点。如果需要在产品族中添加一种全新的产品类型(例如,在UI组件族中加入一个新的
Slider),就需要修改AbstractFactory接口及其所有具体实现类,这违反了开闭原则。
- 难以支持“新种类产品”: 这是最大的缺点。如果需要在产品族中添加一种全新的产品类型(例如,在UI组件族中加入一个新的
2. 使用反射或配置化
通过配置文件(如XML、Properties)或依赖注入容器来指定要创建的具体工厂类,从而避免在代码中硬编码 new ConcreteFactory1()。
// 简单示例:通过属性文件获取工厂类名
public class FactoryProvider {
private static final String FACTORY_TYPE = getFactoryTypeFromConfig(); // 从配置读取
public static AbstractFactory getFactory() {
switch (FACTORY_TYPE) {
case "Modern":
return new ModernFactory();
case "Classic":
return new ClassicFactory();
default:
throw new IllegalArgumentException("Unknown factory type");
}
// 或者使用反射: return (AbstractFactory) Class.forName(className).newInstance();
}
}
// 客户端:AbstractFactory factory = FactoryProvider.getFactory();
- 优点:
- 更高的灵活性: 可以在不重新编译代码的情况下切换整个产品族。
- 缺点:
- 增加了配置的复杂性。
- 类型安全问题依然存在。
六、最佳实践
- 将工厂实现为单例: 一个具体工厂实例通常是无状态的,并且在整个应用中只需要一个实例。因此,通常将其实现为单例。
- 优先使用依赖注入(DI): 不要手动实现抽象工厂模式! 在现代应用中,应直接使用 Spring等IoC容器 来充当这个“超级抽象工厂”。你通过配置(XML或注解)来定义哪个具体产品(Bean)属于哪个“族”(Profile/Configuration),容器会自动完成装配。这是抽象工厂模式思想的最佳实践。
- 明确边界: 只有在确实存在“产品族”概念,且需要保证族内产品的一致性时,才使用抽象工厂模式。如果产品之间没有这种强关联关系,使用多个工厂方法可能更合适。
- 应对“新产品”扩展问题: 如果预见到产品类型可能会频繁增加,可以考虑对工厂接口使用“参数化创建”方法(如
createProduct(String type)),但这会牺牲类型安全性和清晰性,需谨慎权衡。
七、在开发中的演变和应用
抽象工厂模式的思想是现代化框架和开发的基石:
- IoC容器(Spring Framework): Spring的
ApplicationContext是终极的抽象工厂实现。它管理着大量的Bean(产品),并且可以根据不同的配置(如不同的@Profile)来提供完全不同的一套Bean实现(整个产品族)。例如,为“dev”环境提供一组基于内存数据库的DAO实现,为“prod”环境提供一组基于MySQL数据库的DAO实现。 - 跨平台开发与适配: 该模式非常适合屏蔽不同平台或技术的差异。例如,一个图形应用使用抽象工厂接口,其具体实现可以是
WindowsFactory,MacFactory,LinuxFactory,它们分别创建各自平台原生风格的UI组件。客户端代码无需改变即可跨平台运行。 - 微服务配置: 在微服务架构中,为不同部署环境(如中国区、欧美区)提供一套完全不同的中间件客户端配置(如Redis、MQ),也可以视为抽象工厂模式的应用。
八、真实开发案例(Java语言内部、知名开源框架、工具)
-
Java XML处理 (JAXP):
javax.xml.parsers.DocumentBuilderFactory是一个抽象工厂。- 其
newInstance()方法返回一个具体工厂实例(如com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl)。 - 这个具体工厂可以创建一系列相关的产品:
DocumentBuilder,SchemaFactory等,它们全部来自Xerces这个产品族。
-
Java数据库连接 (JDBC):
java.sql.Connection接口可以看作是一个抽象工厂。- 当你通过
DriverManager.getConnection(url)获取一个具体的Connection对象(如MySQL的ConnectionImpl)时,这个具体连接就是一个具体工厂。 - 它可以创建一系列与MySQL相关的Statement产品族对象:
Statement,PreparedStatement,CallableStatement。 - 切换数据库驱动(URL)就意味着切换了整个SQL语句产品的实现族。
-
Spring Framework:
@Profile注解: 这是抽象工厂模式最典型的应用。
@Configuration public class AppConfig { @Bean @Profile("dev") // 开发环境产品族 public DataSource devDataSource() { return new EmbeddedDatabaseBuilder().build(); } @Bean @Profile("prod") // 生产环境产品族 public DataSource prodDataSource() { return new MySqlDataSource(); // 假设的MySQL实现 } }通过激活不同的Profile,Spring容器会提供完全不同的一套数据源、仓库实现等,完美体现了抽象工厂的精神。
-
日志门面 (SLF4J):
- SLF4J作为门面,本身不实现日志功能。它绑定不同的日志实现(如Logback, Log4j 2)。
- 每个绑定包(如
slf4j-log4j12.jar)就相当于一个具体工厂,它提供了一整套与Log4j 1.2相关的日志产品(Logger, Appender等)。切换绑定包就切换了整个日志实现族。
九、总结
| 方面 | 总结 |
|---|---|
| 模式类型 | 创建型设计模式 |
| 核心意图 | 创建相关或依赖对象的家族,而无需指定具体类,保证产品间的一致性。 |
| 关键角色 | 抽象工厂 (AbstractFactory)、具体工厂 (ConcreteFactory)、抽象产品 (AbstractProduct)、具体产品 (ConcreteProduct) |
| 主要优点 | 1. 强一致性:确保产品族内的产品兼容。 2. 解耦客户端与具体类:客户端只面向接口编程。 3. 易于切换产品族:通过更换具体工厂即可。 4. 符合开闭原则(对产品族):支持新增产品族。 |
| 主要缺点 | 1. 难以支持“新产品类型”:增加新产品需修改所有工厂接口和类,违反开闭原则。 2. 代码复杂度高:需要大量的类和接口。 |
| 适用场景 | 1. 系统需要配置多个产品族中的一种。 2. 需要保证一系列相关产品一起使用。 3. 需要提供一个产品库,并只想暴露接口。 |
| 关系与对比 | vs. 工厂方法: 抽象工厂通常通过多个工厂方法实现;工厂方法创建一种产品,抽象工厂创建产品族。 vs. 建造者模式: 建造者关注如何分步创建一个复杂对象,而抽象工厂关注创建多个不同对象。 |
| 现代应用 | Spring IoC容器是其思想的最佳实践,通过配置管理整个Bean“产品族”。 |
抽象工厂模式是架构师设计大型、可配置、可扩展系统的重要工具。它通过定义清晰的抽象接口和产品族概念,极大地提升了系统的模块化水平和可维护性。在现代开发中,其思想已融入各种IoC容器和框架中,成为软件开发的基石之一。
本文来自博客园,作者:NeoLshu,转载请注明原文链接:https://www.cnblogs.com/neolshu/p/19120795

浙公网安备 33010602011771号