配套代码:GitHub 仓库 · 本篇完整源码 · 行为测试。使用 JDK 25 与 Gradle,包名为 com.hanserwei.patterns.abstractfactory

系列导航:Java 25 设计模式学习指南

上一篇:工厂方法 · 下一篇:建造者

博客后台支持浅色和深色主题,每个主题都有按钮和横幅。分别调用按钮工厂、横幅工厂时,很容易给深色页面装上浅色按钮。组件各自都能工作,但页面整体不一致。

产品族意味着若干种不同组件必须一起遵守同一个系列约定。这里选择主题就是选择一组匹配的产品,而不是只选择一个产品的具体类。

从问题提炼设计意图

抽象工厂提供创建一组相关产品的接口,使客户端能够整体切换产品族,而不直接依赖各产品的具体类。

对象职责与协作关系

示例角色 职责
ThemeFactory 抽象工厂,声明按钮和横幅两个创建方法
LightThemeFactory、DarkThemeFactory 具体工厂,分别创建一个主题族
Button、Banner 两类抽象产品
LightButton、LightBanner、DarkButton、DarkBanner 两个产品族中的四个具体产品
classDiagram
    ThemeFactory <|.. LightThemeFactory
    ThemeFactory <|.. DarkThemeFactory
    Button <|.. DarkButton
    Banner <|.. DarkBanner
    DarkThemeFactory ..> DarkButton : creates
    DarkThemeFactory ..> DarkBanner : creates

代码思路:变化应该落在哪个对象上

先找两条轴:产品种类是 Button 与 Banner,产品族是 Light 与 Dark。ThemeFactory 的方法对应产品种类,具体工厂对应产品族。画出这张二维表,比记住“工厂的工厂”更有帮助。

调用方只保存一个 ThemeFactory 引用,从它取得所有需要协作的组件。DarkThemeFactory 的两个方法分别创建 DarkButton 和 DarkBanner;共同的工厂来源落实了本例的主题一致性。

为了突出产品族,组件只返回 theme 名称,没有把 DOM、颜色系统和 UI 框架混入示例。真实组件会提供 render、layout 等行为,但仍应围绕调用方需要的能力设计接口。

关键实现与独立运行

配套仓库中的包名是 com.hanserwei.patterns.abstractfactory,源码目录为 src/main/java/com/hanserwei/patterns/abstractfactory/。仓库地址统一见系列导航。以下展示关键文件的完整内容;其余角色和测试在同一仓库中,每个顶级类型各占一个文件。

ThemeFactory.java

package com.hanserwei.patterns.abstractfactory;

/** 一次选择一整族相互匹配的主题组件. */
public interface ThemeFactory {
  /** 创建主题按钮. */
  Button createButton();

  /** 创建同主题横幅. */
  Banner createBanner();
}

DarkThemeFactory.java

package com.hanserwei.patterns.abstractfactory;

/** 保证两个产品属于同一主题的具体工厂. */
public final class DarkThemeFactory implements ThemeFactory {
  /** 创建本主题按钮. */
  @Override
  public Button createButton() {
    return new DarkButton();
  }

  /** 创建本主题横幅. */
  @Override
  public Banner createBanner() {
    return new DarkBanner();
  }
}

DarkButton.java

package com.hanserwei.patterns.abstractfactory;

/** 主题族内的具体组件. */
public final class DarkButton implements Button {
  /** 返回该组件所属主题. */
  @Override
  public String theme() {
    return "dark";
  }
}

Demo.java 展示调用方如何装配这些对象:

package com.hanserwei.patterns.abstractfactory;

/** 演示本章对象的装配方式和可观察结果. */
public final class Demo {
  /** 禁止实例化演示入口. */
  private Demo() {}

  /** 运行独立示例;args 为未使用的命令行参数. */
  public static void main(String[] args) {
    ThemeFactory factory = new DarkThemeFactory();
    System.out.println(factory.createButton().theme() + "/" + factory.createBanner().theme());
  }
}

在配套代码仓库根目录运行;Windows 使用 gradlew.bat 替换 ./gradlew

./gradlew runAbstractfactory
./gradlew test --tests 'com.hanserwei.patterns.abstractfactory.PatternTest'

示例的业务输出如下,省略 Gradle 自身的任务提示:

dark/dark

用测试确认模式的行为

每个工厂创建的按钮与横幅主题一致;切换工厂同时改变两个组件。测试应覆盖全部工厂,而不只验证深色工厂。

对应测试位于 src/test/java/com/hanserwei/patterns/abstractfactory/PatternTest.java。建议先运行现有测试,再改动一个协作环节,观察哪个断言能够发现问题。

常见用法

  • 同一设计系统中的一组 UI 组件。
  • 不同存储后端各自提供匹配的连接、事务与命令对象。
  • 面向多个平台时,整体替换文件、窗口或输入设备的实现族。

适用边界与容易踩的坑

增加新主题比较顺畅:为各产品种类提供新实现,再增加一个主题工厂。增加新产品种类则代价相反:例如新增 Dialog,ThemeFactory 及所有主题工厂都需要扩展。这是抽象工厂最重要的变化方向判断。

接口本身不会禁止具体工厂错误地混搭产品。本例用家族一致性测试守住约定;复杂系统还可能通过类型参数或受控构造强化限制。不要把“使用了接口”当作已经证明兼容性。

客户端仍可绕过工厂直接 new 组件,所以架构约束需要清晰的装配入口。在不需要产品族一致性的场景,多个独立工厂或直接依赖注入可能更简单。

与相近模式比较

工厂方法通常以一个创建步骤为中心;抽象工厂以一整族创建能力为中心。抽象工厂的实现内部也可以使用工厂方法,但两者的设计意图不同。

动手练习

先增加 HighContrast 产品族,记录需要新增哪些类。再增加 Dialog 产品种类,记录必须修改哪些已有接口和类。用这两份改动清单解释抽象工厂的扩展优势与成本。

系列导航:Java 25 设计模式学习指南

上一篇:工厂方法 · 下一篇:建造者