理解设计模式:抽象工厂抽象工厂:基本思想抽象工厂模式:何时使用抽象工厂模式:优点和缺点抽象工厂模式示例示例 1:抽象工厂模式的基本结构示例 2 - 创建视频游戏的英雄装备结论

2025-06-08

理解设计模式:抽象工厂

抽象工厂:基本思想

抽象工厂模式:何时使用

抽象工厂模式:优点和缺点

抽象工厂模式示例

例1:抽象工厂模式的基本结构

示例 2 - 创建视频游戏的英雄装备

结论

原著《设计模式:可复用面向对象软件的元素》中描述了23种经典的设计模式。这些模式为软件开发中经常出现的特定问题提供了解决方案。

在本文中,我将描述抽象工厂模式的工作原理以及何时应用它。

--

抽象工厂:基本思想

维基百科为我们提供了如下定义:

抽象工厂模式提供了一种方法来封装一组具有共同主题的独立工厂,而无需指定它们的具体类——维基百科

另一方面,原书给出的定义如下:

提供一个接口,用于创建相关或依赖的对象系列,而无需指定其具体类 - 设计模式:可重用面向对象软件的元素

很多时候,我们需要从一系列可能的对象中创建不同类型的对象,而这些对象在创建过程中是相互关联的,因此我们无法预先知道这些对象的类型。自然的趋势是创建一个factoryManager类,使我们能够根据参数获取不同类型的对象。然而,这种解决方案有两个严重的缺点,我们将在本文中逐一描述:

  1. 它违反了开放封闭原则,导致代码不干净;并且当软件扩展时不易维护。

  2. 该类factoryManager附加到您想要构建的所有类型的对象,从而创建称为意大利面条式代码的代码。

这篇文章探讨了这个问题及其解决方案,其中介绍了工厂方法设计模式。当对象的创建过程比较简单且彼此之间不相关时,该模式可以解决这个问题。因此,建议您先阅读这篇文章,然后再探讨抽象工厂模式。

抽象工厂模式可以使代码更清晰,因为它避免了前面提到的问题。该模式的UML图如下:

本书设计模式中的 UML 图表:可重用面向对象软件的元素。

组成该模式的类如下:

  • AbstractProductAAbstractProductB是一组相同类型但不同系列产品的接口。换句话说,所有实现该类的产品都AbstractProductA属于同一产品类型,尽管它们会被组织到不同的系列中。在接下来的具体示例中,我们将更好地理解这种类型的对象。

  • ProductA1ProductA2ProductB1ProductB分别是每种类型的具体实现AbstractProduct

  • AbstractFactory是声明每个具体工厂(ConcreteFactory1ConcreteFactory2)的创建方法集合的接口。

  • ConcreteFactory1ConcreteFactory2AbstractFactory为每个产品系列实现类的创建方法。


抽象工厂模式:何时使用

  1. 抽象工厂模式解决的问题与工厂方法模式类似,但对需要创建的对象类型进行了更深入的抽象。因此,抽象工厂模式需要处理多个相互关联的产品系列,而不是一组产品。

  2. 客户端必须使用的对象系列并非先验已知的。相反,这种知识直接取决于另一个用户(最终用户或系统)与系统的交互。

  3. 如果需要扩展内部组件(创建的系列和对象的数量),则无需耦合代码,而是具有允许轻松扩展工厂和特定产品的接口和抽象。


抽象工厂模式:优点和缺点

抽象工厂模式有很多优点,可以概括为以下几点:

  • 保证同一个工厂类创建的产品之间的兼容性。

  • 由于开放封闭原则可以保证代码整洁,因此可以在不破坏现有代码的情况下引入新的产品系列。

  • 由于遵循单一责任原则 (SRP),创建具体产品的责任转移到具体的创建者类,而不是由客户类承担这一责任,因此代码更清晰。

  • 代码更清晰,因为遵循单一责任原则 (SRP),因为创建具体产品的责任转移到具体的创建者类,而不是由客户类承担此责任。

然而,与大多数设计模式一样,抽象工厂模式的主要缺点是代码的复杂性增加,以及代码所需类的数量增加。虽然,这个缺点在应用设计模式时是众所周知的,因为它是获得代码抽象的代价。


抽象工厂模式示例

接下来我们来说明两个抽象工厂模式的应用示例:

  1. 抽象工厂模式的基本结构。在本例中,我们将把理论上的 UML 图转换成 TypeScript 代码,以识别该模式中涉及的每个类。

  2. 在电子游戏中创建角色。让我们回想一下经典的《魔兽世界》 (WoW ),玩家可以根据自己选择的种族拥有一系列物品。例如,我们会有以下三个种族:人类、兽人和魔法师;他们拥有的武器和盔甲(物品)会根据种族(物品家族)的不同而有所差异。

以下示例将展示如何使用TypeScript实现此模式。我们选择使用 TypeScript 而不是 JavaScript 来实现此模式——后者缺乏接口或抽象类,因此实现接口和抽象类的责任都落在了开发人员身上。

例1:抽象工厂模式的基本结构

在第一个示例中,我们将把理论 UML 图转换为 TypeScript,以测试此模式的潜力。这是要实现的图:

抽象工厂模式的基本结构类图。

首先,我们将定义接口(AbstractProductAAbstractProductB),它们分别定义我们想要为不同产品系列创建的具体产品类型。在我们的具体示例中,为了尽可能简化对模式的理解,每个接口仅定义了一个方法:usefulFunctionAusefulFunctionB

export interface AbstractProductA {
  usefulFunctionA(): string;
}
Enter fullscreen mode Exit fullscreen mode
export interface AbstractProductB {
  usefulFunctionB(): string;
}
Enter fullscreen mode Exit fullscreen mode

下一步是定义实现每个接口的具体产品。在我们的例子中,每个抽象类将实现两个具体对象。对于第一个接口(AbstractProductA),实现了类ConcreteProductA1ConcreteProductA2;而对于第二个接口( ) ,实现了AbstractProductBConcreteProductB1和。ConcreteProductB2

import { AbstractProductA } from "./abstract-productA";

export class ConcreteProductA1 implements AbstractProductA {
  public usefulFunctionA(): string {
    return "The result of the product A1.";
  }
}
Enter fullscreen mode Exit fullscreen mode
import { AbstractProductA } from "./abstract-productA";

export class ConcreteProductA2 implements AbstractProductA {
  public usefulFunctionA(): string {
    return "The result of the product A2.";
  }
}
Enter fullscreen mode Exit fullscreen mode
import { AbstractProductB } from "./abstract-productB";

export class ConcreteProductB1 implements AbstractProductB {
  public usefulFunctionB(): string {
    return "The result of the product B1.";
  }
}
Enter fullscreen mode Exit fullscreen mode
import { AbstractProductB } from "./abstract-productB";

export class ConcreteProductB2 implements AbstractProductB {
  public usefulFunctionB(): string {
    return "The result of the product B2.";
  }
}
Enter fullscreen mode Exit fullscreen mode

一旦定义了与产品创建相关的类的结构,我们接下来就需要定义与负责创建这些对象的工厂相关的类的结构。因此,首先要定义一个抽象类,其中定义了负责由具体工厂创建具体对象的方法。但请注意,这些方法会返回每个产品AbstractFactory对应的抽象类AbstractProductAAbstractProductB

import { AbstractProductA } from "./abstract-productA";
import { AbstractProductB } from "./abstract-productB";

export interface AbstractFactory {
  createProductA(): AbstractProductA;
  createProductB(): AbstractProductB;
}
Enter fullscreen mode Exit fullscreen mode

最后,需要定义具体工厂,用于实例化具体类。在第一个示例中,ConcreteFactory1工厂将负责实例化族 1(ConcreteProductA1ConcreteProductB1)的具体对象,ConcreteFactory2工厂也将负责实例化族 2(ConcreteProductA2ConcreteProductB2)的具体对象。

import { AbstractFactory } from "./abstract-factory";
import { AbstractProductA } from "./abstract-productA";
import { AbstractProductB } from "./abstract-productB";
import { ConcreteProductA1 } from "./concrete-productA1";
import { ConcreteProductB1 } from "./concrete-productB1";

export class ConcreteFactory1 implements AbstractFactory {
  public createProductA(): AbstractProductA {
    return new ConcreteProductA1();
  }

  public createProductB(): AbstractProductB {
    return new ConcreteProductB1();
  }
}
Enter fullscreen mode Exit fullscreen mode
import { AbstractFactory } from "./abstract-factory";
import { AbstractProductA } from "./abstract-productA";
import { AbstractProductB } from "./abstract-productB";
import { ConcreteProductA2 } from "./concrete-productA2";
import { ConcreteProductB2 } from "./concrete-productB2";

export class ConcreteFactory2 implements AbstractFactory {
  public createProductA(): AbstractProductA {
    return new ConcreteProductA2();
  }

  public createProductB(): AbstractProductB {
    return new ConcreteProductB2();
  }
}
Enter fullscreen mode Exit fullscreen mode

虽然它不是模式的直接组成部分,但有必要了解类如何执行该模式Client/Context。在这种情况下,该ClientCode方法不需要知道创建产品的具体工厂,只需接收AbstractFactory类的对象作为参数即可执行CreateProductACreateProductB方法。

import { AbstractFactory } from "./abstract-factory";
import { ConcreteFactory1 } from "./concrete-factory1";
import { ConcreteFactory2 } from "./concrete-factory2";

function clientCode(factory: AbstractFactory) {
  const productA = factory.createProductA();
  const productB = factory.createProductB();

  console.log(productA.usefulFunctionA());
  console.log(productB.usefulFunctionB());
}

console.log("Client: Testing client code with ConcreteFactory1");
clientCode(new ConcreteFactory1());

console.log("----------------");

console.log("Client: Testing the same client code with ConcreteFactory2");
clientCode(new ConcreteFactory2());
Enter fullscreen mode Exit fullscreen mode

示例 2 - 创建视频游戏的英雄装备

我们已经看过此模式的理论示例,因此您已经了解此模式中每个类的职责。现在,我们将说明一个真实的例子,在其中我们将识别此设计模式的每个类。

我们的问题在于如何表示电子游戏中不同英雄或角色的装备。我们将重点关注经典的魔兽世界(WoW )电子游戏,其中的英雄分为三个种族:人类、兽人和法师。每个英雄可以拥有不同的盔甲(armor)和武器(weapon),具体数量取决于种族。因此,我们已经可以确定要构建的产品将是不同类型的盔甲和武器,而产品系列则是针对人类、兽人和法师的产品系列。

因此,按照我们在前面的例子中提出的相同方法,我们将首先查看 UML 图,这将有助于我们识别该模式的每个部分。

类图:视频游戏

先验地,这个问题的类设计可能令人印象深刻,但如果我们已经理解了这个模式的基本结构的例子,我们就会完美地理解这个例子。

我们将从创建每个具体的产品类型开始。也就是说,首先要定义的是模拟武器的接口(weapon)。

export interface Weapon {
  usefulFunction(): string;
}
Enter fullscreen mode Exit fullscreen mode

为了简化示例,我们usefulFunction为每种武器仅定义了一个方法,称为weapons。因此,定义的具体武器是swordaxemage-fireball

import { Weapon } from "./weapon.interface";

export class Sword implements Weapon {
  public usefulFunction(): string {
    return "The result of the Sword";
  }
}
Enter fullscreen mode Exit fullscreen mode
import { Weapon } from "./weapon.interface";

export class Axe implements Weapon {
  public usefulFunction(): string {
    return "The result of the Axe";
  }
}
Enter fullscreen mode Exit fullscreen mode
import { Weapon } from "./weapon.interface";

export class MageFireball implements Weapon {
  public usefulFunction(): string {
    return "The result of the MageFireball";
  }
}
Enter fullscreen mode Exit fullscreen mode

与定义 的方式相同,定义weapon不同的盔甲( )。在这个特定案例中,我们通过调用一个方法在盔甲( )和武器( )之间创建了协作,以说明对象可以相互关联。需要注意的是,协作者参数属于抽象类,而不是具体类。armorarmorweaponusefulFunctionWithWeaponWeapon

import { Weapon } from "../weapons/weapon.interface";

export interface Armor {
  usefulFunction(): string;
  usefulFunctionWithWeapon(collaborator: Weapon): string;
}
Enter fullscreen mode Exit fullscreen mode

我们解决问题所需的特定盔甲是BodyArmorOrcArmor并且Cloak将由每个对象家族根据英雄的种族创建。

import { Armor } from "./armor-interface";
import { Weapon } from "../weapons/weapon.interface";

export class BodyArmor implements Armor {
  public usefulFunction(): string {
    return "The result of the BodyArmor";
  }

  public usefulFunctionWithWeapon(collaborator: Weapon): string {
    const result = collaborator.usefulFunction();
    return `The result of the BodyAmor collaborating with the (${result})`;
  }
}
Enter fullscreen mode Exit fullscreen mode
import { Armor } from "./armor-interface";
import { Weapon } from "../weapons/weapon.interface";

export class OrcArmor implements Armor {
  public usefulFunction(): string {
    return "The result of the OrcArmor";
  }

  public usefulFunctionWithWeapon(collaborator: Weapon): string {
    const result = collaborator.usefulFunction();
    return `The result of the OrcAmor collaborating with the (${result})`;
  }
}
Enter fullscreen mode Exit fullscreen mode
import { Armor } from "./armor-interface";
import { Weapon } from "../weapons/weapon.interface";

export class Cloak implements Armor {
  public usefulFunction(): string {
    return "The result of the Cloak";
  }

  public usefulFunctionWithWeapon(collaborator: Weapon): string {
    const result = collaborator.usefulFunction();
    return `The result of the Cloak collaborating with the (${result})`;
  }
}
Enter fullscreen mode Exit fullscreen mode

到目前为止,我们已经定义了想要在游戏中创建的具体产品,但尚未确定创建规则。具体的工厂将负责根据英雄的种族创建特定的产品。第一个要定义的类是抽象类,AbstractFactory它定义了createWeaponcreateAmor方法,负责创建抽象WeaponArmor产品。请注意,到目前为止的所有代码都使用了抽象类。

import { Armor } from "./armor/armor-interface";
import { Weapon } from "./weapons/weapon.interface";

export interface AbstractFactory {
  createWeapon(): Weapon;
  createArmor(): Armor;
}
Enter fullscreen mode Exit fullscreen mode

这时,我们必须实现具体工厂HumanFactoryOrcFactory其中MageFactory创建者方法根据英雄的种族使用具体产品来实现。

import { AbstractFactory } from "./abstract-factory";
import { Armor } from "./armor/armor-interface";
import { BodyArmor } from "./armor/body-armor.model";
import { Sword } from "./weapons/sword.model";
import { Weapon } from "./weapons/weapon.interface";

export class WarriorFactory implements AbstractFactory {
  public createWeapon(): Weapon {
    return new Sword();
  }

  public createArmor(): Armor {
    return new BodyArmor();
  }
}
Enter fullscreen mode Exit fullscreen mode
import { AbstractFactory } from "./abstract-factory";
import { Armor } from "./armor/armor-interface";
import { Axe } from "./weapons/axe.model";
import { OrcArmor } from "./armor/orc-armor.model";
import { Weapon } from "./weapons/weapon.interface";

export class OrcFactory implements AbstractFactory {
  public createWeapon(): Weapon {
    return new Axe();
  }

  public createArmor(): Armor {
    return new OrcArmor();
  }
}
Enter fullscreen mode Exit fullscreen mode
import { AbstractFactory } from "./abstract-factory";
import { Armor } from "./armor/armor-interface";
import { Cloak } from "./armor/cloak.model";
import { MageFireball } from "./weapons/mage-fireball.model";
import { Weapon } from "./weapons/weapon.interface";

export class MageFactory implements AbstractFactory {
  public createWeapon(): Weapon {
    return new MageFireball();
  }

  public createArmor(): Armor {
    return new Cloak();
  }
}
Enter fullscreen mode Exit fullscreen mode

为了总结创建英雄装备的例子,我们将实现该Client/Context类别。

import { AbstractFactory } from "./abstract-factory";
import { MageFactory } from "./mage-factory";
import { OrcFactory } from "./orc-factory";
import { WarriorFactory } from "./warrior-factory";

function clientCode(factory: AbstractFactory) {
  const sword = factory.createWeapon();
  const armor = factory.createArmor();

  console.log(armor.usefulFunction());
  console.log(armor.usefulFunctionWithWeapon(sword));
}

console.log("Client: WarriorFactory");
clientCode(new WarriorFactory());

console.log("----------------");

console.log("Client: OrcFactory");
clientCode(new OrcFactory());

console.log("----------------");

console.log("Client: MageFactory");
clientCode(new MageFactory());
Enter fullscreen mode Exit fullscreen mode

最后,我创建了两个npm scripts,可以通过它们执行本文中介绍的代码:

    npm run example1
    npm run example2
Enter fullscreen mode Exit fullscreen mode

GitHub Repo 可在此处获取。

结论

抽象工厂是一种遵循开放封闭原则的设计模式,它使用多态性将创建对象的责任委托给特定的类(具体工厂)。这使得我们的代码更加简洁,扩展性更强。

此模式解决了需要创建不同类型的对象时出现的问题,这些对象取决于客户端与系统的交互,而事先并不知道客户端将创建哪个对象。此外,这些对象通过对象家族关联,这样在使用不同的工厂时,可以根据上下文或对象类型将它们区分开来。

这种模式的另一个优点是系统不与一组具体类耦合,但客户端只与抽象类通信,从而允许在软件扩展时拥有更易于维护的代码。

最后,关于这个模式最重要的不是它的具体实现,而是能够识别这个模式能够解决的问题,以及何时可以应用它。具体的实现是其中最不重要的,因为它会根据所使用的编程语言而有所不同。

鏂囩珷鏉ユ簮锛�https://dev.to/carlillo/understanding-design-patterns-abstract-factory-23e7
PREV
通过构建 UI 框架学习 JavaScript:第 5 部分 - 向 Dom 元素添加事件
NEXT
第一部分:打卡上下班系统 - 图表 GenAI LIVE! | 2025年6月4日