抽象工厂模式:产品族与数据库访问层切换
抽象工厂模式,说白了就是解决「一系列相关对象」的创建问题。你想想看,如果只是单个对象的创建,用简单工厂或者工厂方法就够了。但当你需要创建一组互相搭配的产品时,抽象工厂就派上用场了。
我记得刚入行那会儿,接手过一个老项目,数据库从 SQL Server 要迁移到 Oracle。那代码改得叫一个痛苦——每个 new 关键字都得找出来替换。后来我学乖了,这种跨平台、跨数据库的场景,抽象工厂就是标准答案。
产品族与产品等级结构
先理清两个核心概念:产品族和产品等级结构。
- 产品等级结构:同一类产品的继承体系。比如「按钮」这个抽象类,下面有 Windows 按钮、Mac 按钮。
- 产品族:由同一个工厂生产的一组产品。比如 Windows 工厂生产 Windows 按钮 + Windows 文本框 + Windows 菜单栏。
抽象工厂模式要解决的,就是当你有多个产品等级结构,且它们之间存在「配套使用」的关系时,如何保证客户端始终使用同一族的产品。
核心要点:抽象工厂模式封装了产品族的创建逻辑。客户端不需要知道具体产品类名,只需要知道工厂接口。
抽象工厂接口与具体工厂
抽象工厂接口定义了创建每个产品等级结构的方法。具体工厂则实现这些方法,返回特定产品族的实例。
我在项目中遇到过一种情况:一开始只用了工厂方法模式,后来产品族扩展了,不得不把工厂方法改造成抽象工厂。嗯,这个教训告诉我——如果预见到会有产品族出现,直接上抽象工厂反而省事。
// 抽象产品:按钮
interface Button {
void render();
void onClick();
}
// 抽象产品:文本框
interface TextField {
void display();
void input(String text);
}
// 抽象工厂接口
interface UIFactory {
Button createButton();
TextField createTextField();
}
// 具体产品族:Windows
class WindowsButton implements Button {
public void render() { System.out.println("渲染 Windows 风格按钮"); }
public void onClick() { System.out.println("Windows 按钮点击"); }
}
class WindowsTextField implements TextField {
public void display() { System.out.println("显示 Windows 文本框"); }
public void input(String text) { System.out.println("Windows 输入: " + text); }
}
// 具体工厂:Windows 工厂
class WindowsFactory implements UIFactory {
public Button createButton() { return new WindowsButton(); }
public TextField createTextField() { return new WindowsTextField(); }
}
// 具体产品族:Mac
class MacButton implements Button {
public void render() { System.out.println("渲染 Mac 风格按钮"); }
public void onClick() { System.out.println("Mac 按钮点击"); }
}
class MacTextField implements TextField {
public void display() { System.out.println("显示 Mac 文本框"); }
public void input(String text) { System.out.println("Mac 输入: " + text); }
}
class MacFactory implements UIFactory {
public Button createButton() { return new MacButton(); }
public TextField createTextField() { return new MacTextField(); }
}
数据库访问层切换案例
这是抽象工厂最经典的应用场景。假设你的系统需要支持 MySQL、PostgreSQL、Oracle 三种数据库。每种数据库都有连接、命令、事务等操作,这些操作之间是配套的——你不能用 MySQL 的连接去执行 Oracle 的命令。
// 抽象产品
interface DBConnection {
void connect();
void disconnect();
}
interface DBCommand {
void execute(String sql);
void setConnection(DBConnection conn);
}
// 抽象工厂
interface DatabaseFactory {
DBConnection createConnection();
DBCommand createCommand();
}
// MySQL 产品族
class MySQLConnection implements DBConnection {
public void connect() { System.out.println("连接 MySQL"); }
public void disconnect() { System.out.println("断开 MySQL"); }
}
class MySQLCommand implements DBCommand {
private DBConnection conn;
public void setConnection(DBConnection conn) { this.conn = conn; }
public void execute(String sql) { System.out.println("MySQL 执行: " + sql); }
}
class MySQLFactory implements DatabaseFactory {
public DBConnection createConnection() { return new MySQLConnection(); }
public DBCommand createCommand() { return new MySQLCommand(); }
}
// PostgreSQL 产品族
class PostgreSQLConnection implements DBConnection {
public void connect() { System.out.println("连接 PostgreSQL"); }
public void disconnect() { System.out.println("断开 PostgreSQL"); }
}
class PostgreSQLCommand implements DBCommand {
private DBConnection conn;
public void setConnection(DBConnection conn) { this.conn = conn; }
public void execute(String sql) { System.out.println("PostgreSQL 执行: " + sql); }
}
class PostgreSQLFactory implements DatabaseFactory {
public DBConnection createConnection() { return new PostgreSQLConnection(); }
public DBCommand createCommand() { return new PostgreSQLCommand(); }
}
// 客户端代码
class DatabaseClient {
private DatabaseFactory factory;
public DatabaseClient(DatabaseFactory factory) {
this.factory = factory;
}
public void runQuery(String sql) {
DBConnection conn = factory.createConnection();
DBCommand cmd = factory.createCommand();
cmd.setConnection(conn);
conn.connect();
cmd.execute(sql);
conn.disconnect();
}
}
我的经验:切换数据库时,只需要换一行代码——new DatabaseClient(new PostgreSQLFactory())。整个系统的数据库访问逻辑完全不用改。这就是抽象工厂的魅力。
抽象工厂模式的结构图
下面这张图展示了抽象工厂模式的核心结构。你看,客户端只依赖抽象工厂和抽象产品,具体工厂和具体产品都被隔离在另一侧。
使用抽象工厂的注意事项
我曾经踩过的坑:抽象工厂模式有个明显的缺点——扩展产品等级结构很困难。比如你要新增一个「数据库连接池」产品,就得修改抽象工厂接口,所有具体工厂都得跟着改。这就是所谓的「开闭原则」的倾斜性:对产品族扩展友好,对产品等级结构扩展不友好。
所以我的建议是:
- 如果产品等级结构比较稳定,不太会新增,用抽象工厂很合适
- 如果经常要新增产品等级结构,考虑用工厂方法模式或者结合 IoC 容器
- 不要为了用模式而用模式——我见过有人把只有两个产品的场景硬套抽象工厂,反而增加了复杂度
抽象工厂 vs 工厂方法
| 对比维度 | 工厂方法模式 | 抽象工厂模式 |
|---|---|---|
| 创建对象数量 | 单个产品 | 多个相关产品(产品族) |
| 产品等级结构 | 一个 | 多个 |
| 扩展方向 | 新增产品等级结构容易 | 新增产品族容易 |
| 典型应用 | 日志记录器、文件解析器 | 数据库访问层、UI 组件库 |
说白了,工厂方法是「一条生产线生产一种产品」,抽象工厂是「一个工厂生产一整套产品」。你想想看,如果你的系统需要切换数据库,是不是每次都要换一整套——连接、命令、事务、连接池?这就是抽象工厂的用武之地。
个人习惯:我在设计框架时,通常会先定义抽象工厂接口,然后提供默认的具体工厂实现。这样既保留了扩展性,又不会让初学者觉得太复杂。等需要切换时,写一个新的具体工厂类就行了。
抽象工厂模式是设计模式中「创建型」的集大成者。它把产品族的创建逻辑集中管理,让客户端代码与具体实现彻底解耦。虽然它有一定的学习曲线,但一旦用对地方,带来的维护便利性是非常可观的。
公众号:蓝海资料掘金营,微信deep3321