10. 工厂模式:从简单到抽象,再到实战

工厂模式,说实话,是Java里最常用、也最容易“用错”的设计模式之一。我见过太多项目,一开始图省事,new得飞起,结果后期需求一变,改代码改到想哭。工厂模式的核心思想就一句话:把创建对象的逻辑抽出来,交给专门的“工厂”去管

今天咱们就把它讲透。从最简单的简单工厂,到工厂方法,再到抽象工厂,最后来个实战——数据库连接工厂。嗯,一步步来。

10.1 简单工厂:最朴素的“代工厂”

简单工厂,说白了就是一个类负责创建所有相关的对象。你给它一个参数,它返回你要的东西。

举个例子。假设我们要做一个日志系统,支持文件日志和数据库日志。

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

// 文件日志实现
public class FileLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("写入文件: " + message);
    }
}

// 数据库日志实现
public class DatabaseLogger implements Logger {
    @Override
    public void log(String message) {
        System.out.println("写入数据库: " + message);
    }
}

// 简单工厂
public class LoggerFactory {
    public static Logger createLogger(String type) {
        if ("file".equalsIgnoreCase(type)) {
            return new FileLogger();
        } else if ("database".equalsIgnoreCase(type)) {
            return new DatabaseLogger();
        }
        throw new IllegalArgumentException("未知的日志类型: " + type);
    }
}

使用起来很简单:

Logger logger = LoggerFactory.createLogger("file");
logger.log("这是一条日志");

优点:把创建逻辑集中管理,客户端不用关心具体实现类。

缺点:每次新增类型,都要改工厂类的if-else。违反了“开闭原则”。

我个人习惯:简单工厂适合产品种类少、变化不频繁的场景。比如工具类、常量类。一旦超过5种,我就考虑升级了。

10.2 工厂方法:把“选择权”交给子类

简单工厂的痛点在于“改代码”。工厂方法模式就是来解决这个问题的。它把工厂抽象化,每个具体产品对应一个具体工厂。

还是日志的例子,我们用工厂方法重写:

// 抽象工厂
public interface LoggerFactory {
    Logger createLogger();
}

// 文件日志工厂
public class FileLoggerFactory implements LoggerFactory {
    @Override
    public Logger createLogger() {
        return new FileLogger();
    }
}

// 数据库日志工厂
public class DatabaseLoggerFactory implements LoggerFactory {
    @Override
    public Logger createLogger() {
        return new DatabaseLogger();
    }
}

客户端使用:

LoggerFactory factory = new FileLoggerFactory();
Logger logger = factory.createLogger();
logger.log("工厂方法模式");

核心变化:新增一种日志类型,只需要新增一个产品类和一个工厂类,不用改已有代码。完美符合开闭原则。

我曾经在一个项目中,团队用了简单工厂管理20多种支付渠道。每次新增渠道,都要改工厂类,改完还要回归测试所有渠道。后来重构为工厂方法,虽然类多了,但维护成本直线下降。嗯,这就是代价与收益的权衡。

10.3 抽象工厂:创建“产品族”

工厂方法解决的是“单个产品”的创建。但如果你的产品有多个维度呢?比如不同操作系统下的按钮和文本框。

抽象工厂就是用来创建“产品族”的。它定义了一个工厂接口,这个接口可以创建多个相关的产品。

// 抽象产品:按钮
public interface Button {
    void render();
}

// 抽象产品:文本框
public interface TextField {
    void display();
}

// Windows风格实现
public class WindowsButton implements Button {
    @Override
    public void render() {
        System.out.println("渲染Windows按钮");
    }
}

public class WindowsTextField implements TextField {
    @Override
    public void display() {
        System.out.println("显示Windows文本框");
    }
}

// Mac风格实现
public class MacButton implements Button {
    @Override
    public void render() {
        System.out.println("渲染Mac按钮");
    }
}

public class MacTextField implements TextField {
    @Override
    public void display() {
        System.out.println("显示Mac文本框");
    }
}

// 抽象工厂
public interface UIFactory {
    Button createButton();
    TextField createTextField();
}

// Windows工厂
public class WindowsUIFactory implements UIFactory {
    @Override
    public Button createButton() {
        return new WindowsButton();
    }
    @Override
    public TextField createTextField() {
        return new WindowsTextField();
    }
}

// Mac工厂
public class MacUIFactory implements UIFactory {
    @Override
    public Button createButton() {
        return new MacButton();
    }
    @Override
    public TextField createTextField() {
        return new MacTextField();
    }
}

客户端使用:

UIFactory factory = new WindowsUIFactory();
Button button = factory.createButton();
TextField textField = factory.createTextField();
button.render();
textField.display();

适用场景:当你的系统需要支持多套“产品族”(比如不同数据库、不同UI风格、不同云平台),并且这些产品之间有关联时,抽象工厂是首选。

你想想看,如果不用抽象工厂,你要怎么保证Windows按钮和Windows文本框一起出现?靠if-else硬编码?那代码会变成一团乱麻。

10.4 实战:数据库连接工厂

好了,理论讲完了。咱们来点实际的。假设你要做一个支持多种数据库的系统,比如MySQL、PostgreSQL、Oracle。每个数据库的连接方式不同,但使用方式应该统一。

这里我选择工厂方法模式,因为数据库类型相对稳定,但每种数据库的连接细节可能变化。

// 数据库连接接口
public interface DatabaseConnection {
    void connect();
    void disconnect();
    void executeQuery(String sql);
}

// MySQL实现
public class MySQLConnection implements DatabaseConnection {
    private String url;
    private String username;
    private String password;

    public MySQLConnection(String url, String username, String password) {
        this.url = url;
        this.username = username;
        this.password = password;
    }

    @Override
    public void connect() {
        System.out.println("连接到MySQL: " + url);
        // 实际连接逻辑
    }

    @Override
    public void disconnect() {
        System.out.println("断开MySQL连接");
    }

    @Override
    public void executeQuery(String sql) {
        System.out.println("MySQL执行: " + sql);
    }
}

// PostgreSQL实现
public class PostgreSQLConnection implements DatabaseConnection {
    // 类似实现...
}

// 抽象工厂
public interface DatabaseFactory {
    DatabaseConnection createConnection(String url, String username, String password);
}

// MySQL工厂
public class MySQLFactory implements DatabaseFactory {
    @Override
    public DatabaseConnection createConnection(String url, String username, String password) {
        return new MySQLConnection(url, username, password);
    }
}

// PostgreSQL工厂
public class PostgreSQLFactory implements DatabaseFactory {
    @Override
    public DatabaseConnection createConnection(String url, String username, String password) {
        return new PostgreSQLConnection(url, username, password);
    }
}

客户端使用:

// 通过配置决定使用哪种数据库
String dbType = config.getProperty("db.type");
DatabaseFactory factory;

if ("mysql".equalsIgnoreCase(dbType)) {
    factory = new MySQLFactory();
} else if ("postgresql".equalsIgnoreCase(dbType)) {
    factory = new PostgreSQLFactory();
} else {
    throw new IllegalArgumentException("不支持的数据库类型");
}

DatabaseConnection connection = factory.createConnection(url, username, password);
connection.connect();
connection.executeQuery("SELECT * FROM users");
connection.disconnect();

这里有个小技巧:工厂的创建本身也可以封装起来。比如用一个“工厂的工厂”或者配置文件来管理。我习惯用Spring的@Bean来管理这些工厂实例,这样连if-else都省了。

10.5 三种工厂模式对比

模式 核心思想 适用场景 开闭原则
简单工厂 一个工厂类创建所有产品 产品种类少,变化不频繁 违反
工厂方法 每个产品对应一个工厂 产品种类多,但产品等级结构单一 符合
抽象工厂 一个工厂创建一组相关产品 需要创建产品族,产品之间有约束 符合

注意:不要为了用模式而用模式。如果项目只有一种数据库,直接new就完了,别搞什么工厂。过度设计比没有设计更可怕。

10.6 知识体系图

工厂模式知识体系 简单工厂 工厂方法 抽象工厂 特点 • 一个工厂类 • 通过参数判断 • 违反开闭原则 特点 • 每个产品一个工厂 • 符合开闭原则 • 类数量增多 特点 • 创建产品族 • 产品间有约束 • 扩展产品族困难 实战:数据库连接工厂 选择原则:从简单到复杂,按需演进,避免过度设计

这张图把三种工厂模式的关系和特点梳理清楚了。从左到右,复杂度递增,灵活性也递增。实战中,我建议从简单工厂开始,当发现if-else越来越多时,果断重构为工厂方法。至于抽象工厂,等你真的需要管理“产品族”时再用。

核心总结:工厂模式的核心就是“封装变化”。把对象的创建逻辑从业务代码中剥离出来,让代码更清晰、更易维护。记住,模式是工具,不是目的。用对了,事半功倍;用错了,画蛇添足。


公众号:蓝海资料掘金营,微信deep3321