抽象工厂模式:跨平台UI组件库
大家好,我是你们的老朋友。今天我们来聊聊抽象工厂模式。
说实话,这个模式在我刚接触设计模式那会儿,总觉得跟工厂方法模式差不多。后来在做一个跨平台桌面应用时,才真正体会到它的威力。你想想看,如果你的应用要同时跑在Windows、Mac、Linux上,每个平台的按钮长得都不一样,文本框的样式也各有特色,这时候该怎么办?
抽象工厂模式就是专门解决这个问题的。它提供了一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。说白了,就是帮你把「一组产品」的生产过程封装起来。
为什么需要抽象工厂?
我先说说我在项目里遇到的真实情况。当时我们要开发一个文档编辑器,需要支持Windows和Mac两个平台。一开始,我图省事,直接在代码里写if-else判断操作系统:
if (platform == "Windows") {
button = new WindowsButton();
textBox = new WindowsTextBox();
} else if (platform == "Mac") {
button = new MacButton();
textBox = new MacTextBox();
}
刚开始还好,只有按钮和文本框。后来加了菜单、滚动条、对话框……每个组件都要判断一次平台。代码变得又臭又长,而且每次新增一个平台(比如Linux),就要改遍所有地方。嗯,这显然不是好办法。
抽象工厂模式就是用来解决这种「产品族」问题的。它把一组相关的产品(比如Windows下的按钮、文本框、菜单)打包到一个工厂里,客户端只需要跟这个工厂打交道,不用关心具体是哪个平台。
核心结构
抽象工厂模式有四个角色:
- 抽象工厂(AbstractFactory):声明创建一组产品对象的接口
- 具体工厂(ConcreteFactory):实现抽象工厂,创建具体的产品对象
- 抽象产品(AbstractProduct):定义一类产品的接口
- 具体产品(ConcreteProduct):实现抽象产品,定义具体产品
我习惯用一张图来理解它们的关系:
代码实现
我们来看看具体怎么实现。先定义抽象产品接口:
// 抽象按钮
public interface Button {
void render();
void onClick();
}
// 抽象文本框
public interface TextBox {
void render();
String getText();
}
然后实现具体产品:
// Windows按钮
public class WindowsButton implements Button {
@Override
public void render() {
System.out.println("渲染Windows风格按钮");
}
@Override
public void onClick() {
System.out.println("Windows按钮点击事件");
}
}
// Mac按钮
public class MacButton implements Button {
@Override
public void render() {
System.out.println("渲染Mac风格按钮");
}
@Override
public void onClick() {
System.out.println("Mac按钮点击事件");
}
}
// Windows文本框
public class WindowsTextBox implements TextBox {
@Override
public void render() {
System.out.println("渲染Windows风格文本框");
}
@Override
public String getText() {
return "Windows文本框内容";
}
}
// Mac文本框
public class MacTextBox implements TextBox {
@Override
public void render() {
System.out.println("渲染Mac风格文本框");
}
@Override
public String getText() {
return "Mac文本框内容";
}
}
接下来是抽象工厂和具体工厂:
// 抽象工厂
public interface UIFactory {
Button createButton();
TextBox createTextBox();
}
// Windows工厂
public class WindowsFactory implements UIFactory {
@Override
public Button createButton() {
return new WindowsButton();
}
@Override
public TextBox createTextBox() {
return new WindowsTextBox();
}
}
// Mac工厂
public class MacFactory implements UIFactory {
@Override
public Button createButton() {
return new MacButton();
}
@Override
public TextBox createTextBox() {
return new MacTextBox();
}
}
客户端使用起来就清爽多了:
public class Application {
private Button button;
private TextBox textBox;
public Application(UIFactory factory) {
button = factory.createButton();
textBox = factory.createTextBox();
}
public void render() {
button.render();
textBox.render();
}
}
// 使用示例
public class Main {
public static void main(String[] args) {
// 根据配置选择工厂
UIFactory factory;
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
factory = new WindowsFactory();
} else {
factory = new MacFactory();
}
Application app = new Application(factory);
app.render();
}
}
核心要点:客户端只依赖抽象工厂和抽象产品,不依赖任何具体类。新增一个平台时,只需要添加新的具体工厂和具体产品,不用修改现有代码。
适用场景
抽象工厂模式不是万能的,我个人觉得它最适合以下场景:
- 产品族约束:一组产品必须一起使用,比如同一主题下的按钮、文本框、菜单
- 系统独立于产品创建:不想让客户端代码知道具体创建了哪些产品
- 产品族扩展:未来可能增加新的产品族(比如支持Linux平台)
避坑指南
我曾经踩过的坑:一开始我把所有UI组件都塞进一个抽象工厂里,导致工厂接口变得非常庞大。后来才意识到,抽象工厂的粒度要适中。如果产品族太大,可以考虑拆分成多个小工厂。
另外,抽象工厂模式有个天然的缺点:扩展产品种类很困难。比如你想在现有工厂里加一个「下拉框」产品,那就得修改所有具体工厂。所以,在设计之初就要想清楚产品族里包含哪些产品,尽量保持稳定。
与工厂方法模式的区别
很多初学者容易把这两个模式搞混。我简单说说我的理解:
| 对比维度 | 工厂方法模式 | 抽象工厂模式 |
|---|---|---|
| 产品数量 | 只创建一种产品 | 创建一组相关产品(产品族) |
| 工厂数量 | 每个产品对应一个工厂 | 每个产品族对应一个工厂 |
| 扩展方向 | 容易扩展新产品 | 容易扩展新产品族 |
| 典型场景 | 日志记录器、数据库驱动 | 跨平台UI组件、主题皮肤 |
小技巧:如果你发现代码里频繁出现「if-else判断平台类型,然后创建一组对象」,那就是抽象工厂模式登场的时候了。
好了,抽象工厂模式就聊到这里。这个模式在实际项目中用得非常多,尤其是做跨平台开发或者主题切换功能的时候。记住它的核心思想:封装产品族的创建过程,让客户端与具体产品解耦。
公众号:蓝海资料掘金营,微信deep3321