5. 工厂模式(下):抽象工厂模式、产品族与产品等级结构、工厂模式在数据库连接池中的应用
好,咱们接着聊工厂模式。上一章我们把简单工厂和工厂方法掰开揉碎讲了一遍。今天要聊的抽象工厂模式,才是真正让我当年在项目中「眼前一亮」的东西。
说实话,我第一次接触抽象工厂时,觉得这东西太绕了。又是产品族又是产品等级,搞得跟生物分类似的。直到后来在一个跨数据库的项目里栽了跟头,才真正理解它的价值。
5.1 产品族与产品等级结构
先搞清楚两个概念。这俩词听着唬人,其实特简单。
产品等级结构,说白了就是同一类产品的继承关系。比如「按钮」是一个抽象产品,Windows按钮、Mac按钮就是具体产品。它们属于同一个等级结构。
产品族,指的是由同一个工厂生产的一组产品。比如Windows平台下,有Windows按钮、Windows文本框、Windows菜单栏。这些产品虽然功能不同,但都属于「Windows风格」这个产品族。
我画了张图,你看一眼就明白了:
纵向看,是产品等级结构。横向看,就是产品族。抽象工厂要解决的,就是如何保证同一个产品族内的产品能协同工作。
5.2 抽象工厂模式的核心思想
抽象工厂模式,说白了就是「工厂的工厂」。它提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
我举个例子。假设你在做一个跨平台的UI框架。Windows下按钮是圆角的,Mac下是胶囊形的,Linux下是方形的。文本框、菜单栏也各有各的样式。你不能让Windows按钮配上Mac文本框吧?那画面太美我不敢看。
抽象工厂就是用来解决这个问题的。它保证你从同一个工厂拿到的所有产品,都属于同一个产品族。
核心要点:抽象工厂模式适用于「一系列相互依赖的产品」的创建场景。它把产品族的约束关系,从调用方转移到了工厂内部。
5.3 代码实现
咱们直接上代码。还是UI框架的例子:
// 第一步:定义抽象产品
// 按钮接口
public interface IButton {
void render();
}
// 文本框接口
public interface ITextBox {
void render();
}
// 第二步:实现具体产品
// Windows风格
public class WindowsButton implements IButton {
public void render() {
System.out.println("渲染Windows风格按钮");
}
}
public class WindowsTextBox implements ITextBox {
public void render() {
System.out.println("渲染Windows风格文本框");
}
}
// Mac风格
public class MacButton implements IButton {
public void render() {
System.out.println("渲染Mac风格按钮");
}
}
public class MacTextBox implements ITextBox {
public void render() {
System.out.println("渲染Mac风格文本框");
}
}
// 第三步:定义抽象工厂
public interface IUIFactory {
IButton createButton();
ITextBox createTextBox();
}
// 第四步:实现具体工厂
public class WindowsFactory implements IUIFactory {
public IButton createButton() {
return new WindowsButton();
}
public ITextBox createTextBox() {
return new WindowsTextBox();
}
}
public class MacFactory implements IUIFactory {
public IButton createButton() {
return new MacButton();
}
public ITextBox createTextBox() {
return new MacTextBox();
}
}
// 第五步:客户端使用
public class Application {
private IButton button;
private ITextBox textBox;
public Application(IUIFactory factory) {
button = factory.createButton();
textBox = factory.createTextBox();
}
public void render() {
button.render();
textBox.render();
}
}
// 调用方只需要指定工厂类型
public class Main {
public static void main(String[] args) {
// 根据配置选择工厂
IUIFactory factory = new WindowsFactory();
Application app = new Application(factory);
app.render();
}
}
个人经验:我在项目中习惯把工厂的选择逻辑封装成一个配置类。比如从配置文件读取「os.type=windows」,然后通过反射或策略模式加载对应的工厂。这样新增一个平台时,只需要加一个工厂类和对应的产品类,完全不用改现有代码。
5.4 工厂模式在数据库连接池中的应用
聊完理论,咱们看看实际项目里怎么用。数据库连接池是个很好的例子。
我记得有一次,公司要支持多种数据库。MySQL、PostgreSQL、Oracle,每种数据库的连接参数、验证方式、连接池配置都不一样。如果每个地方都写if-else,那代码就炸了。
用工厂模式怎么设计?看下面这张图:
代码实现大概是这样的:
// 抽象产品
public interface IConnection {
void connect();
void close();
}
public interface IConnectionPoolConfig {
int getMaxConnections();
int getTimeout();
}
// 抽象工厂
public interface IConnectionPoolFactory {
IConnection createConnection();
IConnectionPoolConfig createConfig();
}
// MySQL具体工厂
public class MySQLConnectionPoolFactory implements IConnectionPoolFactory {
public IConnection createConnection() {
return new MySQLConnection();
}
public IConnectionPoolConfig createConfig() {
return new MySQLConfig();
}
}
// 客户端代码
public class DatabaseManager {
private IConnectionPoolFactory factory;
public DatabaseManager(String dbType) {
// 根据数据库类型选择工厂
if ("mysql".equals(dbType)) {
factory = new MySQLConnectionPoolFactory();
} else if ("postgresql".equals(dbType)) {
factory = new PostgreSQLConnectionPoolFactory();
}
}
public void init() {
IConnectionPoolConfig config = factory.createConfig();
IConnection conn = factory.createConnection();
// 使用config初始化连接池
// 使用conn进行数据库操作
}
}
避坑指南:我曾经在一个项目里,把工厂选择逻辑写在了调用方。结果后来要新增一个数据库类型,改了十几个文件。正确的做法是把工厂选择逻辑封装在配置中心或依赖注入容器里。调用方只依赖抽象工厂,永远不知道具体实现是谁。
5.5 抽象工厂 vs 工厂方法
很多同学会搞混这两个模式。我简单总结一下:
| 对比维度 | 工厂方法模式 | 抽象工厂模式 |
|---|---|---|
| 创建对象数量 | 一个产品等级结构 | 多个产品等级结构 |
| 产品关系 | 产品之间无关联 | 产品属于同一产品族 |
| 扩展方式 | 新增具体工厂和产品 | 新增具体工厂和一组产品 |
| 典型场景 | 日志记录器、文件导出 | 跨平台UI、数据库连接池 |
说白了,工厂方法解决的是「单个产品的创建」问题。抽象工厂解决的是「一组产品的创建」问题。如果你的产品之间没有依赖关系,用工厂方法就够了。如果它们必须成双成对出现,那就得上抽象工厂。
我的建议:不要为了用模式而用模式。我见过有人把只有一个产品的场景也硬套抽象工厂,结果代码复杂度翻了三倍。记住,设计模式是工具,不是目的。能用简单工厂解决的问题,就别上抽象工厂。
好了,抽象工厂模式就聊到这儿。核心就三句话:产品族要统一管理,工厂要抽象化,调用方要依赖倒置。下一章我们聊观察者模式,那个在事件驱动系统里特别实用。
公众号:蓝海资料掘金营,微信deep3321