五、抽象工厂模式:定义、结构、实现、产品族与产品等级结构、优缺点、应用场景
抽象工厂模式,说实话,是我在实际项目中用得最多的创建型模式之一。它解决了一个很现实的问题:当你的系统需要支持多套产品组合时,怎么让代码不变成一团乱麻。
我记得有一次接手一个跨平台UI框架的项目,Windows、macOS、Linux三套界面风格,每个平台都有按钮、文本框、下拉菜单。如果硬编码,那代码里全是if-else判断平台类型。后来我用抽象工厂重构了,整个世界清静了。
5.1 定义
抽象工厂模式,说白了就是提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
你想想看,工厂方法模式解决的是单个产品的创建问题。但现实中,我们往往需要创建一组配套的产品。比如一套UI组件,按钮和文本框必须风格统一。抽象工厂就是为这种场景设计的。
5.2 结构
抽象工厂模式的结构,我习惯把它拆成四个角色来看:
- 抽象工厂(AbstractFactory):声明创建抽象产品对象的操作接口
- 具体工厂(ConcreteFactory):实现创建具体产品对象的操作
- 抽象产品(AbstractProduct):为一类产品对象声明接口
- 具体产品(ConcreteProduct):定义具体工厂创建的产品对象
嗯,这里要注意:抽象工厂里的每个方法,其实都是一个工厂方法。所以抽象工厂可以看作是工厂方法的集合。
核心逻辑:一个具体工厂负责生产一族产品,这些产品之间是配套的、互相关联的。
下面这张图是我自己画的,把抽象工厂的结构理得很清楚:
5.3 实现
咱们直接上代码。还是用跨平台UI组件的例子:
// 抽象产品:按钮
public interface IButton {
void Render();
}
// 抽象产品:文本框
public interface ITextBox {
void Render();
}
// 具体产品:Windows按钮
public class WindowsButton : IButton {
public void Render() {
Console.WriteLine("渲染Windows风格按钮");
}
}
// 具体产品:Windows文本框
public class WindowsTextBox : ITextBox {
public void Render() {
Console.WriteLine("渲染Windows风格文本框");
}
}
// 具体产品:macOS按钮
public class MacButton : IButton {
public void Render() {
Console.WriteLine("渲染macOS风格按钮");
}
}
// 具体产品:macOS文本框
public class MacTextBox : ITextBox {
public void Render() {
Console.WriteLine("渲染macOS风格文本框");
}
}
// 抽象工厂
public interface IUIFactory {
IButton CreateButton();
ITextBox CreateTextBox();
}
// 具体工厂:Windows工厂
public class WindowsFactory : IUIFactory {
public IButton CreateButton() {
return new WindowsButton();
}
public ITextBox CreateTextBox() {
return new WindowsTextBox();
}
}
// 具体工厂:macOS工厂
public class MacFactory : IUIFactory {
public IButton CreateButton() {
return new MacButton();
}
public ITextBox CreateTextBox() {
return new MacTextBox();
}
}
// 客户端使用
public class Application {
private IUIFactory _factory;
private IButton _button;
private ITextBox _textBox;
public Application(IUIFactory factory) {
_factory = factory;
}
public void CreateUI() {
_button = _factory.CreateButton();
_textBox = _factory.CreateTextBox();
}
public void Render() {
_button.Render();
_textBox.Render();
}
}
个人经验:我在实际项目中,通常会把工厂的选择逻辑封装在一个配置类里。比如从配置文件读取平台类型,然后通过反射创建对应的工厂实例。这样新增一个平台,只需要加一个工厂类和对应的产品类,完全不用改客户端代码。
5.4 产品族与产品等级结构
这两个概念,我刚开始学的时候也容易搞混。咱们用个表格说清楚:
| 概念 | 定义 | 例子 |
|---|---|---|
| 产品等级结构 | 同一类产品的继承体系 | IButton → WindowsButton、MacButton、LinuxButton |
| 产品族 | 由同一个工厂生产的、相互关联的一组产品 | Windows工厂生产:WindowsButton + WindowsTextBox |
说白了,产品等级结构是纵向的,产品族是横向的。抽象工厂模式的核心,就是让客户端能够切换不同的产品族,而不用关心每个产品等级结构里的具体实现。
5.5 优缺点
优点:
- 隔离具体类:客户端只和抽象接口打交道,具体产品类被隔离在工厂后面
- 保证产品族一致性:一个工厂生产的产品必然是配套的,不会出现Windows按钮配macOS文本框这种乌龙
- 符合开闭原则:新增一个产品族,只需要加一个具体工厂和对应的具体产品类,不用改现有代码
缺点:
- 扩展产品等级结构困难:如果想给所有产品族都加一个新产品(比如加一个下拉菜单),那所有工厂接口和具体工厂都得改
- 系统复杂度增加:每多一个产品等级结构,就要多一套抽象产品和具体产品类
避坑指南:我曾经在一个项目中过度使用抽象工厂,把三个不相关的产品硬塞到一个工厂里。结果后来需求变更,其中一个产品要独立演化,搞得我改得焦头烂额。记住:只有真正属于同一个产品族、需要保持配套关系的产品,才适合放在同一个抽象工厂里。
5.6 应用场景
抽象工厂模式适合以下场景:
- 跨平台UI组件库:每个平台有一套完整的UI组件,必须风格统一
- 数据库访问层:支持多种数据库(MySQL、PostgreSQL、Oracle),每个数据库有自己的连接、命令、事务实现
- 主题换肤系统:每个主题包含颜色、字体、图标等一系列配套资源
- 游戏角色创建:不同种族(人类、精灵、兽人)有各自配套的武器、盔甲、技能
我个人的判断标准很简单:如果我发现代码里有一组if-else,每个分支都在创建多个配套的对象,那就该考虑抽象工厂了。
一句话总结:抽象工厂模式,就是为一组配套产品的创建提供一个统一的入口,让客户端可以轻松切换整个产品族。