框架和库中常用的设计模式:创建型模式之抽象工厂模式

364 阅读5分钟

在TypeScript中使用模式

  • 定义: 抽象工厂模式是一种创建型设计模式,他能创建一系列相关的对象而无需指定其具体类。 抽象工厂定义了用于创建不同产品的接口,但将实际的创建工作留给了具体工厂类。每个工厂类型都对应一个特定的产品。

在创建产品时,客户端代码调用的是工厂对象的构建方法,而不是直接调用构造函数。因为一个工厂对应一种产品变体,因此它创建的所有的产品都可相互兼容。

客户端代码仅通过其抽象接口与工厂和产品进行交互。该接口允许同一客户端代码与不同产品进行交互。你只需要创建一个具体工厂类并将其传递给客户端代码即可。

  • 使用场景:许多框架和程序库会将抽象工厂模式作为扩展和自定义其标准组件的一种常见方式
  • 识别方法: 返回一个工厂对象,每个属性都是一个工厂方法。

示例

//定义一个工厂要能够生产两种家具才可以接单:椅子和沙发
interface AbstractFactory {
    createProductA(): AbstractProductA;//椅子
    createProductB(): AbstractProductB;//沙发
}
//椅子是这样的
interface AbstractProductA {
    usefulFunctionA(): string;
}
//沙发是这样的
interface AbstractProductB {
    usefulFunctionB(): string;
    anotherUserfulFunctionB(collaborator: AbstractProductA): string;
}
//工厂1里面可以生产椅子和沙发,类型为中式
class ConcreteFactory1 implements AbstractFactory {
    public createProductA(): AbstractProductA {
        return new ConcreteProductA1();
    }
    
    public createProductB(): AbstractProductB {
        return new ConcreteProductB1();
    }
}
//工厂2里面可以生产椅子和沙发,类型为欧美
class ConcreteFactory2 implements AbstractFactory {
    public createProductA(): AbstractProductA {
        return new ConcreteProductA2();
    }
    public createProductB(): AbstractProductB {
        return new ConcreteProductB2();
    }
}
//椅子: 中式 and 欧美
class ConcreteProductA1 implements AbstractProductA {
    public usefulFunctionA(): string {
        return 'a1';
    }
}
class ConcreteProductA2 implements AbstractProductA {
    public usefulFunctionA(): string {
        return 'a2';
    }
}
//沙发: 中式 and 欧美
class ConcreteProductB1 implements AbstractProductB {
    public usefulFunctionB(): string {
        return 'b1';
    }
    public anotherUserfulFunctionB(collaborator: AbstractProductA): string {
        const result = collaborator.usefulFunction();
        return result;
    }
}
class ConcreteProductB2 implements AbstractProductB {
    public usefulFunctionB(): string {
        return 'b2';
    }
    public anotherUserfulFunctionB(collaborator: AbstractProductA): string {
        const result = collaborator.usefulFunction();
        return result;
    } 
}

function clientCode(factory: AbstractFactory) {
    const productA = factory.createProductA();
    const productB = factory.createProductB();
    console.log(productB.usefulFunctionB());
    console.log(productB.anotherUserfulFunction(productA));
}

clientCode(new ConcreteFactory1());
clientCode(new ConcreteFactory2());

抽象工厂模式适合应用场景

如果代码需要与多个不同系列的相关产品交互, 但是由于无法提前获取相关信息, 或者出于对未来扩展性的考虑, 你不希望代码基于产品的具体类进行构建, 在这种情况下, 你可以使用抽象工厂。

抽象工厂为你提供了一个接口, 可用于创建每个系列产品的对象。 只要代码通过该接口创建对象, 那么你就不会生成与应用程序已生成的产品类型不一致的产品。

如果你有一个基于一组抽象方法的类, 且其主要功能因此变得不明确, 那么在这种情况下可以考虑使用抽象工厂模式。

在设计良好的程序中, 每个类仅负责一件事。 如果一个类与多种类型产品交互, 就可以考虑将工厂方法抽取到独立的工厂类或具备完整功能的抽象工厂类中。

实现方式

  • 以不同的产品类型与产品变体为维度绘制矩阵。

  • 为所有产品声明抽象产品接口。 然后让所有具体产品类实现这些接口。

  • 声明抽象工厂接口, 并且在接口中为所有抽象产品提供一组构建方法。

  • 为每种产品变体实现一个具体工厂类。

  • 在应用程序中开发初始化代码。 该代码根据应用程序配置或当前环境, 对特定具体工厂类进行初始化。 然后将该工厂对象传递给所有需要创建产品的类。

  • 找出代码中所有对产品构造函数的直接调用, 将其替换为对工厂对象中相应构建方法的调用。

抽象工厂模式优缺点

  • 你可以确保同一工厂生成的产品相互匹配。
  • 你可以避免客户端和具体产品代码的耦合。
  • 单一职责原则。 你可以将产品生成代码抽取到同一位置, 使得代码易于维护。
  • 开闭原则。 向应用程序中引入新产品变体时, 你无需修改客户端代码。
  • 由于采用该模式需要向应用中引入众多接口和类, 代码可能会比之前更加复杂。

与其他模式的关系

  • 在许多设计工作的初期都会使用工厂方法模式 (较为简单, 而且可以更方便地通过子类进行定制), 随后演化为使用抽象工厂模式、 原型模式或生成器模式 (更灵活但更加复杂)。

  • 生成器重点关注如何分步生成复杂对象。 抽象工厂专门用于生产一系列相关对象。 抽象工厂会马上返回产品, 生成器则允许你在获取产品前执行一些额外构造步骤。

  • 抽象工厂模式通常基于一组工厂方法, 但你也可以使用原型模式来生成这些类的方法。

  • 当只需对客户端代码隐藏子系统创建对象的方式时, 你可以使用抽象工厂来代替外观模式。

  • 你可以将抽象工厂和桥接模式搭配使用。 如果由桥接定义的抽象只能与特定实现合作, 这一模式搭配就非常有用。 在这种情况下, 抽象工厂可以对这些关系进行封装, 并且对客户端代码隐藏其复杂性。

  • 抽象工厂、 生成器和原型都可以用单例模式来实现。

设计模式之美: juejin.cn/post/692021…