← 返回题目列表

如何用抽象工厂实现跨平台 UI 组件?

高频 中等 第 10 / 25 题 更新于 2026/07/28
抽象工厂模式跨平台UI产品族Java

简化版

跨平台 UI 可以把 Button、Checkbox 等组件定义成抽象产品,再为 Windows、Mac 等平台定义具体工厂。客户端只依赖 UIFactory,切换具体工厂就能整体切换一套平台风格。

详细版

核心接口可以这样设计:

interface Button {
    void render();
}

interface Checkbox {
    void render();
}

interface UIFactory {
    Button createButton();
    Checkbox createCheckbox();
}

Windows 平台实现:

class WindowsFactory implements UIFactory {
    public Button createButton() {
        return new WindowsButton();
    }

    public Checkbox createCheckbox() {
        return new WindowsCheckbox();
    }
}

客户端代码只拿 UIFactory

class Dialog {
    private final UIFactory factory;

    Dialog(UIFactory factory) {
        this.factory = factory;
    }

    void render() {
        factory.createButton().render();
        factory.createCheckbox().render();
    }
}

这样应用启动时根据操作系统选择 WindowsFactoryMacFactory,业务层不用知道具体组件类。

完整版教学

一、先抽象产品等级

跨平台 UI 里,Button、Checkbox、Dialog 都可以是产品等级。每个产品等级定义自己的抽象接口,客户端只依赖这些接口。

interface Button {
    void onClick();
}

具体产品再实现平台差异,比如 WindowsButton 使用 Windows 风格渲染,MacButton 使用 Mac 风格渲染。

二、再抽象产品族工厂

UIFactory 负责创建同一平台下的一组组件。WindowsFactory 创建 Windows 族组件,MacFactory 创建 Mac 族组件。

这个结构的关键是:客户端不分别选择 ButtonFactory 和 CheckboxFactory,而是选择一个平台工厂,避免组件风格被拆散。

三、客户端如何切换平台

平台选择通常放在应用启动配置中:

UIFactory factory = os.isMac() ? new MacFactory() : new WindowsFactory();
Dialog dialog = new Dialog(factory);

后续所有组件都从同一个工厂创建,风格自然保持一致。

四、新增平台和新增组件的代价不同

新增 Linux 平台时,新增 LinuxButton、LinuxCheckbox、LinuxFactory 即可,原有客户端和抽象接口不需要改。

但如果新增 Menu 组件,就要修改 UIFactory,并让 WindowsFactory、MacFactory、LinuxFactory 都实现 createMenu()。这是抽象工厂的典型代价。

五、客户端只选择一次平台而不是逐组件选择

应用启动时选择 WindowsFactory 后,Dialog 后续创建 Button、Checkbox 都不再读取操作系统分支。这样平台决策只出现一次,新增界面组件时也能检查每个平台是否提供同族实现。

          Button          Checkbox
Windows   WindowsButton   WindowsCheckbox
Mac       MacButton       MacCheckbox
启动配置 os=mac -> MacFactory
Dialog.createButton() -> MacButton
Dialog.createCheckbox() -> MacCheckbox
客户端只依赖 Button/Checkbox 接口
不出现 WindowsButton + MacCheckbox

矩阵的每一行是一族兼容产品,每一列是一个产品等级。客户端选择一行对应的具体工厂,再从该工厂取得各列产品;如果允许调用方绕过工厂分别选择每个具体类,产品族一致性的结构性保证就消失了。

六、二维扩展成本与模式边界

评审维度本题结论
产品族不变量一个 Dialog 构建周期内的组件全部来自同一 UIFactory。
新增一行(产品族)新增 Linux 组件一套和 LinuxFactory,旧客户端保持抽象依赖。
新增一列(产品等级)新增 Menu 需修改 UIFactory 和全部平台工厂。
不应由工厂承担组件渲染状态、事件业务和页面布局。
退回更轻方案的条件只有 Button 一种组件时工厂方法足够;平台原生组件不需切换时直接构造更简单。

抽象工厂对所有变化并非都开放。它刻意固定列集合,以换取整行切换的一致性;如果系统最常发生的是新增列,工厂接口和所有行实现会频繁联动,此时应拆分工厂边界或选择组合方式。

七、落地验收清单

  1. 为每个具体工厂跑同一组契约测试,确认每个创建方法都返回正确产品等级。
  2. 给产品暴露 familyId() 仅用于测试,校验同一工厂创建的所有产品 familyId 一致。
  3. 加入一个假产品族,记录是否只新增一行实现和装配配置,而未修改旧工厂。
  4. 模拟新增一个产品等级,评估抽象接口和全部具体工厂的真实修改成本。
  5. 检查公共产品接口是否出现 AWS、MySQL、Windows 等具体实现词汇。
  6. 检查具体工厂是否只做组装与创建,没有吞入完整业务流程。
  7. 若产品持有连接或线程池,明确由工厂、容器还是调用方负责关闭。
  8. 若运行期按租户切换工厂,验证路由缓存、凭证刷新和租户隔离。

记忆钩子:平台只选一次,组件从同一工厂成套拿。

八、常见误区与追问

  • 误区:客户端可以分别选择每个组件平台。 这会破坏抽象工厂要保证的产品族一致性。
  • 误区:产品族是 Button 的所有实现。 那是产品等级;产品族是同平台的不同组件。
  • 误区:新增 Linux 平台需要修改 WindowsFactory。 现有产品等级稳定时只需新增 Linux 一行。
  • 追问:操作系统判断放哪里? 放应用组合根或配置层,避免散落在每个页面。
  • 追问:组件间需要共享主题怎么办? 把不可变主题配置注入具体工厂,由同族产品共享。
  • 追问:新增 Menu 为什么成本高? 它新增一列,所有平台工厂都必须履行新创建契约。

九、平台选择时序与一致性测试

平台判断应只出现在应用组合根,而不是每个 Dialog 或组件内部。启动时根据 OS 或配置创建一个 UIFactory,再把它注入后续对象,业务层便不会重复出现平台分支。

启动检测 os=mac
    -> 组合根选择 MacFactory
    -> 注入 Dialog
    -> createButton() = MacButton
    -> createCheckbox() = MacCheckbox
    -> 切换为 WindowsFactory 后整套替换

测试时不只验证类型,还要让同一工厂创建的 Button 和 Checkbox 运行共享主题或平台契约。再注入 FakeFactory,可以在不启动真实 UI 工具包的情况下验证 Dialog 只依赖抽象产品。

十、加强记忆

跨平台 UI 的抽象工厂答案要抓住两点:产品等级是 Button、Checkbox,产品族是 Windows、Mac。客户端选的是整套平台工厂,不是零散地选每个组件实现。