理解设计模式:抽象工厂
抽象工厂:基本思想
抽象工厂模式:何时使用
抽象工厂模式:优点和缺点
抽象工厂模式示例
例1:抽象工厂模式的基本结构
示例 2 - 创建视频游戏的英雄装备
结论
原著《设计模式:可复用面向对象软件的元素》中描述了23种经典的设计模式。这些模式为软件开发中经常出现的特定问题提供了解决方案。
在本文中,我将描述抽象工厂模式的工作原理以及何时应用它。
--
抽象工厂:基本思想
维基百科为我们提供了如下定义:
抽象工厂模式提供了一种方法来封装一组具有共同主题的独立工厂,而无需指定它们的具体类——维基百科
另一方面,原书给出的定义如下:
提供一个接口,用于创建相关或依赖的对象系列,而无需指定其具体类 - 设计模式:可重用面向对象软件的元素
很多时候,我们需要从一系列可能的对象中创建不同类型的对象,而这些对象在创建过程中是相互关联的,因此我们无法预先知道这些对象的类型。自然的趋势是创建一个factoryManager
类,使我们能够根据参数获取不同类型的对象。然而,这种解决方案有两个严重的缺点,我们将在本文中逐一描述:
-
它违反了开放封闭原则,导致代码不干净;并且当软件扩展时不易维护。
-
该类
factoryManager
附加到您想要构建的所有类型的对象,从而创建称为意大利面条式代码的代码。
这篇文章探讨了这个问题及其解决方案,其中介绍了工厂方法设计模式。当对象的创建过程比较简单且彼此之间不相关时,该模式可以解决这个问题。因此,建议您先阅读这篇文章,然后再探讨抽象工厂模式。
抽象工厂模式可以使代码更清晰,因为它避免了前面提到的问题。该模式的UML图如下:
组成该模式的类如下:
-
AbstractProductA和AbstractProductB是一组相同类型但不同系列产品的接口。换句话说,所有实现该类的产品都
AbstractProductA
属于同一产品类型,尽管它们会被组织到不同的系列中。在接下来的具体示例中,我们将更好地理解这种类型的对象。 -
ProductA1、ProductA2、ProductB1和ProductB分别是每种类型的具体实现
AbstractProduct
。 -
AbstractFactory是声明每个具体工厂(
ConcreteFactory1
和ConcreteFactory2
)的创建方法集合的接口。 -
ConcreteFactory1和ConcreteFactory2
AbstractFactory
为每个产品系列实现类的创建方法。
抽象工厂模式:何时使用
-
抽象工厂模式解决的问题与工厂方法模式类似,但对需要创建的对象类型进行了更深入的抽象。因此,抽象工厂模式需要处理多个相互关联的产品系列,而不是一组产品。
-
客户端必须使用的对象系列并非先验已知的。相反,这种知识直接取决于另一个用户(最终用户或系统)与系统的交互。
-
如果需要扩展内部组件(创建的系列和对象的数量),则无需耦合代码,而是具有允许轻松扩展工厂和特定产品的接口和抽象。
抽象工厂模式:优点和缺点
抽象工厂模式有很多优点,可以概括为以下几点:
-
保证同一个工厂类创建的产品之间的兼容性。
-
由于开放封闭原则可以保证代码整洁,因此可以在不破坏现有代码的情况下引入新的产品系列。
-
由于遵循单一责任原则 (SRP),创建具体产品的责任转移到具体的创建者类,而不是由客户类承担这一责任,因此代码更清晰。
-
代码更清晰,因为遵循单一责任原则 (SRP),因为创建具体产品的责任转移到具体的创建者类,而不是由客户类承担此责任。
然而,与大多数设计模式一样,抽象工厂模式的主要缺点是代码的复杂性增加,以及代码所需类的数量增加。虽然,这个缺点在应用设计模式时是众所周知的,因为它是获得代码抽象的代价。
抽象工厂模式示例
接下来我们来说明两个抽象工厂模式的应用示例:
-
抽象工厂模式的基本结构。在本例中,我们将把理论上的 UML 图转换成 TypeScript 代码,以识别该模式中涉及的每个类。
-
在电子游戏中创建角色。让我们回想一下经典的《魔兽世界》 (WoW ),玩家可以根据自己选择的种族拥有一系列物品。例如,我们会有以下三个种族:人类、兽人和魔法师;他们拥有的武器和盔甲(物品)会根据种族(物品家族)的不同而有所差异。
以下示例将展示如何使用TypeScript实现此模式。我们选择使用 TypeScript 而不是 JavaScript 来实现此模式——后者缺乏接口或抽象类,因此实现接口和抽象类的责任都落在了开发人员身上。
例1:抽象工厂模式的基本结构
在第一个示例中,我们将把理论 UML 图转换为 TypeScript,以测试此模式的潜力。这是要实现的图:
首先,我们将定义接口(AbstractProductA
和AbstractProductB
),它们分别定义我们想要为不同产品系列创建的具体产品类型。在我们的具体示例中,为了尽可能简化对模式的理解,每个接口仅定义了一个方法:usefulFunctionA
和usefulFunctionB
。
export interface AbstractProductA {
usefulFunctionA(): string;
}
export interface AbstractProductB {
usefulFunctionB(): string;
}
下一步是定义实现每个接口的具体产品。在我们的例子中,每个抽象类将实现两个具体对象。对于第一个接口(AbstractProductA
),实现了类ConcreteProductA1
和ConcreteProductA2
;而对于第二个接口( ) ,实现了AbstractProductB
类ConcreteProductB1
和。ConcreteProductB2
import { AbstractProductA } from "./abstract-productA";
export class ConcreteProductA1 implements AbstractProductA {
public usefulFunctionA(): string {
return "The result of the product A1.";
}
}
import { AbstractProductA } from "./abstract-productA";
export class ConcreteProductA2 implements AbstractProductA {
public usefulFunctionA(): string {
return "The result of the product A2.";
}
}
import { AbstractProductB } from "./abstract-productB";
export class ConcreteProductB1 implements AbstractProductB {
public usefulFunctionB(): string {
return "The result of the product B1.";
}
}
import { AbstractProductB } from "./abstract-productB";
export class ConcreteProductB2 implements AbstractProductB {
public usefulFunctionB(): string {
return "The result of the product B2.";
}
}
一旦定义了与产品创建相关的类的结构,我们接下来就需要定义与负责创建这些对象的工厂相关的类的结构。因此,首先要定义一个抽象类,其中定义了负责由具体工厂创建具体对象的方法。但请注意,这些方法会返回每个产品AbstractFactory
对应的抽象类。AbstractProductA
AbstractProductB
import { AbstractProductA } from "./abstract-productA";
import { AbstractProductB } from "./abstract-productB";
export interface AbstractFactory {
createProductA(): AbstractProductA;
createProductB(): AbstractProductB;
}
最后,需要定义具体工厂,用于实例化具体类。在第一个示例中,ConcreteFactory1
工厂将负责实例化族 1(ConcreteProductA1
和ConcreteProductB1
)的具体对象,ConcreteFactory2
工厂也将负责实例化族 2(ConcreteProductA2
和ConcreteProductB2
)的具体对象。
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();
}
}
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();
}
}
虽然它不是模式的直接组成部分,但有必要了解类如何执行该模式Client/Context
。在这种情况下,该ClientCode
方法不需要知道创建产品的具体工厂,只需接收AbstractFactory
类的对象作为参数即可执行CreateProductA
和CreateProductB
方法。
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());
示例 2 - 创建视频游戏的英雄装备
我们已经看过此模式的理论示例,因此您已经了解此模式中每个类的职责。现在,我们将说明一个真实的例子,在其中我们将识别此设计模式的每个类。
我们的问题在于如何表示电子游戏中不同英雄或角色的装备。我们将重点关注经典的魔兽世界(WoW )电子游戏,其中的英雄分为三个种族:人类、兽人和法师。每个英雄可以拥有不同的盔甲(armor
)和武器(weapon
),具体数量取决于种族。因此,我们已经可以确定要构建的产品将是不同类型的盔甲和武器,而产品系列则是针对人类、兽人和法师的产品系列。
因此,按照我们在前面的例子中提出的相同方法,我们将首先查看 UML 图,这将有助于我们识别该模式的每个部分。
先验地,这个问题的类设计可能令人印象深刻,但如果我们已经理解了这个模式的基本结构的例子,我们就会完美地理解这个例子。
我们将从创建每个具体的产品类型开始。也就是说,首先要定义的是模拟武器的接口(weapon
)。
export interface Weapon {
usefulFunction(): string;
}
为了简化示例,我们usefulFunction
为每种武器仅定义了一个方法,称为weapons
。因此,定义的具体武器是sword
、axe
和mage-fireball
。
import { Weapon } from "./weapon.interface";
export class Sword implements Weapon {
public usefulFunction(): string {
return "The result of the Sword";
}
}
import { Weapon } from "./weapon.interface";
export class Axe implements Weapon {
public usefulFunction(): string {
return "The result of the Axe";
}
}
import { Weapon } from "./weapon.interface";
export class MageFireball implements Weapon {
public usefulFunction(): string {
return "The result of the MageFireball";
}
}
与定义 的方式相同,定义weapon
不同的盔甲( )。在这个特定案例中,我们通过调用一个方法在盔甲( )和武器( )之间创建了协作,以说明对象可以相互关联。需要注意的是,协作者参数属于抽象类,而不是具体类。armor
armor
weapon
usefulFunctionWithWeapon
Weapon
import { Weapon } from "../weapons/weapon.interface";
export interface Armor {
usefulFunction(): string;
usefulFunctionWithWeapon(collaborator: Weapon): string;
}
我们解决问题所需的特定盔甲是BodyArmor
,OrcArmor
并且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})`;
}
}
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})`;
}
}
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})`;
}
}
到目前为止,我们已经定义了想要在游戏中创建的具体产品,但尚未确定创建规则。具体的工厂将负责根据英雄的种族创建特定的产品。第一个要定义的类是抽象类,AbstractFactory
它定义了createWeapon
和createAmor
方法,负责创建抽象Weapon
和Armor
产品。请注意,到目前为止的所有代码都使用了抽象类。
import { Armor } from "./armor/armor-interface";
import { Weapon } from "./weapons/weapon.interface";
export interface AbstractFactory {
createWeapon(): Weapon;
createArmor(): Armor;
}
这时,我们必须实现具体工厂HumanFactory
,OrcFactory
其中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();
}
}
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();
}
}
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();
}
}
为了总结创建英雄装备的例子,我们将实现该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());
最后,我创建了两个npm scripts
,可以通过它们执行本文中介绍的代码:
npm run example1
npm run example2
GitHub Repo 可在此处获取。
结论
抽象工厂是一种遵循开放封闭原则的设计模式,它使用多态性将创建对象的责任委托给特定的类(具体工厂)。这使得我们的代码更加简洁,扩展性更强。
此模式解决了需要创建不同类型的对象时出现的问题,这些对象取决于客户端与系统的交互,而事先并不知道客户端将创建哪个对象。此外,这些对象通过对象家族关联,这样在使用不同的工厂时,可以根据上下文或对象类型将它们区分开来。
这种模式的另一个优点是系统不与一组具体类耦合,但客户端只与抽象类通信,从而允许在软件扩展时拥有更易于维护的代码。
最后,关于这个模式最重要的不是它的具体实现,而是能够识别这个模式能够解决的问题,以及何时可以应用它。具体的实现是其中最不重要的,因为它会根据所使用的编程语言而有所不同。
鏂囩珷鏉ユ簮锛�https://dev.to/carlillo/understanding-design-patterns-abstract-factory-23e7