讲解

买电脑整机时,你不会自己攒 CPU、主板、内存的混搭——品牌整机帮你保证这套配件是互相兼容的「一整套」。抽象工厂模式解决的就是这类问题:要创建的不是单个对象,而是一族相互关联、必须配套的对象,并且要保证客户端不会把不同族的产品混着用。

GoF 的定义:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。结构上,抽象工厂是一个接口,声明了一组「创建产品 A、创建产品 B……」的方法;每个具体工厂对应一个「产品族」(比如 Windows 风格一整套 UI 组件、Mac 风格一整套),客户端只面向抽象工厂编程,换成另一个族时整体替换工厂即可。

它和工厂方法的关系可以用一句话概括:工厂方法是「一个工厂造一种产品」,抽象工厂是「一个工厂造一族配套产品」。很多抽象工厂的实现里,每个创建方法本身就是一个工厂方法。选择标准也清晰:产品之间没有配套约束时用工厂方法,有「必须同一族」约束时用抽象工厂。

典型适用场景:跨平台 UI 组件库(按钮、复选框、下拉框必须同一风格)、数据库访问层(连接、命令、事务必须来自同一驱动)、主题系统(一套配色、字体、间距成套切换)。它的软肋同样明显:产品族里新增一种产品(比如所有 UI 族都要加「开关」组件)时,抽象接口和每个具体工厂都要加方法——对产品种类是封闭的,对产品族是开放的。

示例

跨平台 UI 组件族:UIFactory 一次产出一套风格一致的按钮和复选框,客户端 buildDialog 完全不知道自己在哪个平台上:

import assert from 'node:assert/strict';

interface Button {
  render(): string;
}
interface Checkbox {
  render(): string;
}

class WinButton implements Button {
  render(): string {
    return '[Win 按钮]';
  }
}
class WinCheckbox implements Checkbox {
  render(): string {
    return '[Win 复选框]';
  }
}
class MacButton implements Button {
  render(): string {
    return '(Mac 按钮)';
  }
}
class MacCheckbox implements Checkbox {
  render(): string {
    return '(Mac 复选框)';
  }
}

// 抽象工厂:一次创建一整套风格一致的产品族
interface UIFactory {
  createButton(): Button;
  createCheckbox(): Checkbox;
}

class WinFactory implements UIFactory {
  createButton(): Button {
    return new WinButton();
  }
  createCheckbox(): Checkbox {
    return new WinCheckbox();
  }
}

class MacFactory implements UIFactory {
  createButton(): Button {
    return new MacButton();
  }
  createCheckbox(): Checkbox {
    return new MacCheckbox();
  }
}

// 客户端只面向抽象工厂,不可能混搭出「Win 按钮 + Mac 复选框」
function buildDialog(factory: UIFactory): string {
  return factory.createButton().render() + ' + ' + factory.createCheckbox().render();
}

assert.equal(buildDialog(new WinFactory()), '[Win 按钮] + [Win 复选框]');
assert.equal(buildDialog(new MacFactory()), '(Mac 按钮) + (Mac 复选框)');
console.log(buildDialog(new WinFactory()));
console.log(buildDialog(new MacFactory()));

关键收益在类型系统的帮助下被放大了:buildDialog 的参数类型是 UIFactory,它拿到的按钮和复选框必然来自同一个工厂实例,「混搭出错」这一类 bug 在结构上被消灭了。切换平台只是换一个工厂实例,调用方代码零改动。

常见坑

  • 给不相关的产品也套抽象工厂:产品之间没有「必须配套」约束时,这个模式纯属增加间接层,用工厂方法甚至直接 new 即可。
  • 低估「新增产品种类」的成本:产品族里加一种产品要动抽象接口和所有具体工厂,产品种类频繁膨胀的领域要慎重。
  • 客户端绕过工厂直接 new 具体产品:这等于拆掉了模式的全部保护,团队约定上应禁止在具体工厂之外出现产品类的 new。
  • 和建造者模式混淆:抽象工厂强调「一族配套产品、立即返回」;建造者强调「分步骤装配一个复杂对象」。后面建造者一章会再对比。
  • 工厂实例满天飞:一个运行上下文通常只需要一个工厂实例,入口选定后通过依赖注入往下传,而不是到处 new WinFactory()。

小结

抽象工厂面向「一族配套产品」的创建,保证客户端不会混搭不同族;它对「换族」开放、对「加产品种类」封闭。下一章看创建型里最出名也最容易被滥用的一个:单例模式。