5. 工厂模式(下):抽象工厂模式、产品族与产品等级结构、工厂模式在数据库连接池中的应用

好,咱们接着聊工厂模式。上一章我们把简单工厂和工厂方法掰开揉碎讲了一遍。今天要聊的抽象工厂模式,才是真正让我当年在项目中「眼前一亮」的东西。

说实话,我第一次接触抽象工厂时,觉得这东西太绕了。又是产品族又是产品等级,搞得跟生物分类似的。直到后来在一个跨数据库的项目里栽了跟头,才真正理解它的价值。

5.1 产品族与产品等级结构

先搞清楚两个概念。这俩词听着唬人,其实特简单。

产品等级结构,说白了就是同一类产品的继承关系。比如「按钮」是一个抽象产品,Windows按钮、Mac按钮就是具体产品。它们属于同一个等级结构。

产品族,指的是由同一个工厂生产的一组产品。比如Windows平台下,有Windows按钮、Windows文本框、Windows菜单栏。这些产品虽然功能不同,但都属于「Windows风格」这个产品族。

我画了张图,你看一眼就明白了:

产品族与产品等级结构示意图 抽象按钮 Windows按钮 Mac按钮 Linux按钮 抽象文本框 Windows文本框 Mac文本框 Linux文本框 抽象菜单 Windows菜单 Mac菜单 Linux菜单 产品族(按平台划分) Windows产品族 Mac产品族 Linux产品族 纵向看:产品等级结构(同一类产品) | 横向看:产品族(同一平台的产品集合)

纵向看,是产品等级结构。横向看,就是产品族。抽象工厂要解决的,就是如何保证同一个产品族内的产品能协同工作。

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,那代码就炸了。

用工厂模式怎么设计?看下面这张图:

工厂模式在数据库连接池中的应用 连接池工厂(抽象) MySQL连接池工厂 PostgreSQL连接池工厂 Oracle连接池工厂 MySQL连接对象 PostgreSQL连接对象 Oracle连接对象 MySQL连接池配置 PostgreSQL连接池配置 Oracle连接池配置 每个具体工厂负责创建:连接对象 + 连接池配置 + 验证策略 调用方只需依赖抽象工厂,无需关心具体数据库类型

代码实现大概是这样的:

// 抽象产品
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