抽象工厂模式:跨平台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):实现抽象产品,定义具体产品

我习惯用一张图来理解它们的关系:

抽象工厂 +createButton() +createTextBox() Windows工厂 +createButton() Mac工厂 +createButton() 抽象按钮 +render() +onClick() 抽象文本框 +render() +getText() Windows按钮 Mac按钮 Windows文本框 Mac文本框

代码实现

我们来看看具体怎么实现。先定义抽象产品接口:

// 抽象按钮
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