首页 >web前端 >js教程 >掌握抽象工厂模式:综合指南

掌握抽象工厂模式:综合指南

Susan Sarandon
Susan Sarandon原创
2024-12-05 20:41:14904浏览

您是否曾经发现自己需要在应用程序中创建不同系列对象的多个变体,而无需一遍又一遍地重复逻辑?

或者也许您已经构建了一个应用程序,却发现新的需求或客户更改的偏好需要全新的对象,从而迫使您重新编写整个代码库?

如果有一种方法可以无缝引入新的变体而不破坏现有代码只需插入新的实现会怎么样?

这就是抽象工厂设计模式的用武之地!

在本教程中,我们将通过构建 Node.js CLI 应用程序来分解这种强大的设计模式,用于创建支持多种格式和主题的多种类型的简历。

概述

抽象工厂是一种创意设计模式,它是一类设计模式,用于处理使用新关键字或运算符。

您可以将

抽象工厂设计模式视为我们在本博客文章中介绍的工厂方法设计模式的概括。

问题

抽象工厂设计模式解决了以下问题:

    我们如何创建
  1. 相关产品系列,例如:PDFResumeJSONResumeMarkdownResume
  2. 我们如何支持每个产品系列拥有多个变体,例如:
  3. CreativeResumeMinimalistResumeModernResume
  4. 我们如何支持添加更多变体和产品而不破坏我们现有的消费或客户端代码?
解决方案

抽象工厂设计模式通过为每种类型的产品声明一个接口或抽象类来解决这些问题。

export abstract class PDFResume {}
export abstract class JSONResume {}
export abstract class MarkdownResume {}

然后,正如模式名称所暗示的那样,我们创建一个

抽象工厂 ,它是一个声明 工厂方法 的接口,用于创建每种类型的产品:

  • createPDFResume :返回 PDFResume 类型或子类型。
  • createMarkdownResume :返回 MarkdownResume 类型或子类型。
  • createJSONResume :返回 JSONResume 类型或子类型。
export interface ResumeFactory {
  createPDFResume(): PDFResume
  createMarkdownResume(): MarkdownResume
  createJSONResume(): JSONResume
}

好吧,现在我们有一个通用工厂,它可以返回每种可能类型的产品,但是我们如何支持每个产品的多个变体?

答案是通过创建一个ConcreteFactory来实现抽象工厂ResumeFactory)。

export abstract class PDFResume {}
export abstract class JSONResume {}
export abstract class MarkdownResume {}

现在,要在客户端类中使用我们的工厂,我们只需声明一个ResumeFactory类型的变量,然后根据用户输入实例化相应的具体工厂

客户端代码:

export interface ResumeFactory {
  createPDFResume(): PDFResume
  createMarkdownResume(): MarkdownResume
  createJSONResume(): JSONResume
}

结构

Mastering the Abstract Factory Pattern: A Comprehensive Guide

抽象工厂设计模式的结构由以下类组成:

  1. Factory :将此设计模式命名为 抽象工厂 的原因是该类代表所有 ConcreteFactories 之间的契约。它定义了所有工厂方法
  • 工厂方法的数量等于产品的数量。
  • 每个 工厂方法 应返回抽象或通用产品类型 (IProduct{j} )。

在我们的例子中,Factory 中声明的工厂方法是:createProductA 和 createProductB

  1. ConcreteFactory{i} :这些类实现 Factory 类,并为每个 工厂方法提供自定义实现。
  • 在上述架构中,i 等于 1 或 2。
  • ConcreteFactories 的数量等于每个产品可能的变体数量。
  • 每个具体的工厂方法应该返回一个对象,该对象是相应产品的实例。
  1. IProduct{j} :这些类对应于抽象产品类型。
  • 在上述模式中,j 等于 A 或 B。
  • 每个 IProduct{j} 都是由许多具体的产品类实现的。

ConcretProductA1 和 ConcretProductA2 实现 IProductA ConcretProductB1 和 ConcretProductB2 实现 IProductB

  1. ConcreteProducts 是实现 IProduct{j} 通用类型之一的产品。

实际场景

在本节中,我们将通过构建一个完全工作的 Node.js TypeScript CLI 应用程序来将前面的示例付诸实践,该应用程序根据用户选择的主题和格式创建简历。

请随意在您的计算机上克隆此存储库来查看完整的工作代码。

然后运行以下命令:

export abstract class PDFResume {}
export abstract class JSONResume {}
export abstract class MarkdownResume {}

声明类型

让我们首先声明我们将在整个教程中使用的类型,以确保类型安全。

接口/类型

export interface ResumeFactory {
  createPDFResume(): PDFResume
  createMarkdownResume(): MarkdownResume
  createJSONResume(): JSONResume
}

  1. ResumeData 类型定义了简历对象的所有属性,例如:姓名、电子邮件、电话和一系列经历。
  2. 体验类型包含:公司、职位、开始日期、结束日期和描述。

声明我们的抽象工厂

现在,让我们声明 通用工厂 类型,它将定义三个 工厂方法,它们对应于不同支持的产品类型: PDFResumeMarkdownResume ,以及JSONResume.

接口/ResumeFactory

export class CreativeResumeFactory implements ResumeFactory {
  createPDFResume(): CreativePDFResume {
    return new CreativePDFResume() // CreativePDFResume implements PDFResume
  }

  createMarkdownResume(): CreativeMarkdownResume {
    return new CreativeMarkdownResume() // CreativeMarkdownResume implements MarkdownResume
  }

  createJSONResume(): CreativeJSONResume {
    return new CreativeJSONResume() // CreativeJSONResume implements JSONResume
  }
}

我们将在下一节中详细介绍他们的代码。

声明不同类型文档的共享类

接下来,让我们继续创建通用产品类。

每个产品类型都将是一个抽象类,因为我们希望在其相应的子类型之间共享属性和方法。

  1. JSONResume :该类有一个受保护的 data 属性,存储 ResumeData 类型的对象,并带有一个名为 style.
  2. 的额外属性。

该类定义:

  • 访问 data 属性的 getter 方法。
  • 抽象的generate方法,稍后将被子类覆盖。
  • 具有基本实现的 saveToFile 方法,其中包括将简历数据存储在 JSON 文件中。

简历/json/JSONResume

// User inputs...
let theme = "minimalist"
let format = "pdf"

let factory: ResumeFactory

switch (theme) {
  case "minimalist":
    factory = new MinimalistResumeFactory()
    break
  case "modern":
    factory = new ModernResumeFactory()
    break
  case "creative":
    factory = new CreativeResumeFactory()
    break
  default:
    throw new Error("Invalid theme.")
}

const userInput = await getUserInput()
let resume

switch (format) {
  case "pdf":
    resume = factory.createPDFResume()
    break
  case "markdown":
    resume = factory.createMarkdownResume()
    break
  case "json":
    resume = factory.createJSONResume()
    break
  default:
    throw new Error("Invalid format.")
}

abstract关键字表示该类是泛型类型,无法实例化;它只能被其他类继承。

  1. MarkdownResume :该类有一个受保护的 content 属性,存储 markdown 字符串。

该类定义:

  • 访问 content 属性的 getter 方法。
  • 抽象的generate方法,稍后将被子类覆盖。
  • 一个 saveToFile 方法,它接受一个文件名,然后将 Markdown 格式的字符串 content 存储到一个文件中。

简历/markdown/MarkdownResume

export abstract class PDFResume {}
export abstract class JSONResume {}
export abstract class MarkdownResume {}

  1. PDF简历 :

该类有一个类型为 PDFKit.PDFDocument 的受保护 doc 对象,该对象是从名为 pdfkit 的库导入的。该库通过其面向对象的界面简化了 PDF 文档的创建和操作。

该类定义:

  • 访问 doc 属性的 getter 方法。
  • 抽象的generate方法,稍后将被子类覆盖。
  • saveToFile 方法,将 doc 内存中的 PDF 对象保存到特定文件中。

简历/pdf/PDFResume

export interface ResumeFactory {
  createPDFResume(): PDFResume
  createMarkdownResume(): MarkdownResume
  createJSONResume(): JSONResume
}

宣布我们的混凝土工厂

现在我们已经定义了 通用产品类型抽象工厂 ,是时候继续创建 ConcreteFactories 了,它们对应于每种通用产品类型的不同变体。

我们有 3 种可能的简历变体:创意极简现代。以及 3 种通用产品: JSONPDFMarkdown.

抽象工厂 (ResumeFactory) 定义了 3 个工厂方法,负责创建我们的产品:

  • createPDFResume :创建 PDFResume.
  • 类型的实例
  • createMarkdownResume :创建 MarkdownResume.
  • 类型的实例
  • createJSONResume :创建 JSONResume.
  • 类型的实例

为了支持每个产品的多个变体,我们必须创建 3 个具体工厂

每个混凝土工厂将生产3种类型的产品,但具有自己的风味:

  1. CreativeResumeFactory 创建 Creative 变体的产品。
  2. MinimalistResumeFactory 创建 Minimalist 变体的产品。
  3. ModernResumeFactory 创建现代变体的产品。

工厂/创意简历工厂

export class CreativeResumeFactory implements ResumeFactory {
  createPDFResume(): CreativePDFResume {
    return new CreativePDFResume() // CreativePDFResume implements PDFResume
  }

  createMarkdownResume(): CreativeMarkdownResume {
    return new CreativeMarkdownResume() // CreativeMarkdownResume implements MarkdownResume
  }

  createJSONResume(): CreativeJSONResume {
    return new CreativeJSONResume() // CreativeJSONResume implements JSONResume
  }
}

  • CreativeResumeFactory 工厂方法返回每种类型产品的创意具体产品变体。

工厂/MinimalistResumeFactory

// User inputs...
let theme = "minimalist"
let format = "pdf"

let factory: ResumeFactory

switch (theme) {
  case "minimalist":
    factory = new MinimalistResumeFactory()
    break
  case "modern":
    factory = new ModernResumeFactory()
    break
  case "creative":
    factory = new CreativeResumeFactory()
    break
  default:
    throw new Error("Invalid theme.")
}

const userInput = await getUserInput()
let resume

switch (format) {
  case "pdf":
    resume = factory.createPDFResume()
    break
  case "markdown":
    resume = factory.createMarkdownResume()
    break
  case "json":
    resume = factory.createJSONResume()
    break
  default:
    throw new Error("Invalid format.")
}

  • MinimalistResumeFactory 工厂方法返回每种类型产品的极简具体产品变体。

工厂/ModernResumeFactory

export abstract class PDFResume {}
export abstract class JSONResume {}
export abstract class MarkdownResume {}

  • ModernResumeFactory 工厂方法返回每种产品类型的现代具体产品变体。

创意简历工厂具体产品

现在,让我们创建之前的 ConcreteProducts,它们由 CreativeResumeFactory

返回

PDF 简历 :

简历/pdf/CreativePDFResume

export interface ResumeFactory {
  createPDFResume(): PDFResume
  createMarkdownResume(): MarkdownResume
  createJSONResume(): JSONResume
}

Markdown 简历 :

简历/markdown/CreativeMarkdownResume

export class CreativeResumeFactory implements ResumeFactory {
  createPDFResume(): CreativePDFResume {
    return new CreativePDFResume() // CreativePDFResume implements PDFResume
  }

  createMarkdownResume(): CreativeMarkdownResume {
    return new CreativeMarkdownResume() // CreativeMarkdownResume implements MarkdownResume
  }

  createJSONResume(): CreativeJSONResume {
    return new CreativeJSONResume() // CreativeJSONResume implements JSONResume
  }
}

JSON 简历 :

简历/json/CreativeJSONResume

// User inputs...
let theme = "minimalist"
let format = "pdf"

let factory: ResumeFactory

switch (theme) {
  case "minimalist":
    factory = new MinimalistResumeFactory()
    break
  case "modern":
    factory = new ModernResumeFactory()
    break
  case "creative":
    factory = new CreativeResumeFactory()
    break
  default:
    throw new Error("Invalid theme.")
}

const userInput = await getUserInput()
let resume

switch (format) {
  case "pdf":
    resume = factory.createPDFResume()
    break
  case "markdown":
    resume = factory.createMarkdownResume()
    break
  case "json":
    resume = factory.createJSONResume()
    break
  default:
    throw new Error("Invalid format.")
}

极简简历工厂混凝土产品

接下来,让我们创建之前的 ConcreteProducts,它们由 MinimalistResumeFactory

返回

PDF 简历 :

简历/pdf/MinimalistPDFResume

npm install
npm start

Markdown 简历 :

简历/markdown/MinimalistMarkdownResume

export type ResumeData = {
  name: string
  email: string
  phone: string
  experience: Experience[]
}

export type Experience = {
  company: string
  position: string
  startDate: string
  endDate: string
  description: string
}

JSON 简历 :

resumes/json/MinimalistJSONResume

import { JSONResume } from "../resumes/json/JSONResume"
import { MarkdownResume } from "../resumes/markdown/MarkdownResume"
import { PDFResume } from "../resumes/pdf/PdfResume"

export interface ResumeFactory {
  createPDFResume(): PDFResume
  createMarkdownResume(): MarkdownResume
  createJSONResume(): JSONResume
}

现代简历工厂混凝土产品

最后,让我们创建之前的 ConcreteProducts,它们由 ModernResumeFactory

返回

PDF 简历 :

简历/pdf/ModernPDFResume

import * as fs from "fs/promises"

import { ResumeData } from "../../interfaces/Types"

export abstract class JSONResume {
  protected data!: ResumeData & { style: string }

  abstract generate(data: ResumeData): void

  async saveToFile(fileName: string): Promise<void> {
    await fs.writeFile(fileName, JSON.stringify(this.data, null, 2))
  }

  getData(): any {
    return this.data
  }
}

Markdown 简历 :

简历/markdown/ModernMarkdownResume

import * as fs from "fs/promises"

import { ResumeData } from "../../interfaces/Types"

export abstract class MarkdownResume {
  protected content: string = ""

  abstract generate(data: ResumeData): void

  async saveToFile(fileName: string): Promise<void> {
    await fs.writeFile(fileName, this.content)
  }

  getContent(): string {
    return this.content
  }
}

JSON 简历 :

简历/json/ModernJSONResume

import * as fs from "fs"

import PDFDocument from "pdfkit"

import { ResumeData } from "../../interfaces/Types"

export abstract class PDFResume {
  protected doc: PDFKit.PDFDocument

  constructor() {
    this.doc = new PDFDocument()
  }

  abstract generate(data: ResumeData): void

  async saveToFile(fileName: string): Promise<void> {
    const stream = fs.createWriteStream(fileName)
    this.doc.pipe(stream)
    this.doc.end()

    await new Promise<void>((resolve, reject) => {
      stream.on("finish", resolve)
      stream.on("error", reject)
    })
  }

  getBuffer(): Buffer {
    return this.doc.read() as Buffer
  }
}

在 Index.ts 文件中使用我们的工厂

让我们开始通过在客户端代码中使用我们的工厂来收获之前工作的成果。

看看我们现在如何通过使用我们的工厂以非常干净的方式使用我们的简历构建器库。

用户只需提供两件事:

  1. 产品类型:他想要创建什么类型的 PDF?
  2. 主题:他喜欢什么样的简历风格?

index.ts

import { ResumeFactory } from "../interfaces/ResumeFactory"
import { CreativeJSONResume } from "../resumes/json/CreativeJSONResume"
import { CreativeMarkdownResume } from "../resumes/markdown/CreativeMarkdownResume"
import { CreativePDFResume } from "../resumes/pdf/CreativePDFResume"

export class CreativeResumeFactory implements ResumeFactory {
  createPDFResume(): CreativePDFResume {
    return new CreativePDFResume() // CreativePDFResume extends PDFResume
  }

  createMarkdownResume(): CreativeMarkdownResume {
    return new CreativeMarkdownResume() // CreativeMarkdownResume extends MarkdownResume
  }

  createJSONResume(): CreativeJSONResume {
    return new CreativeJSONResume() // CreativeJSONResume extends JSONResume
  }
}

上面的代码分三步运行:

  1. 用户输入:我们首先获取主题格式值。
  2. 选择工厂 :然后我们根据主题值实例化相应的工厂。
  3. 创建产品:最后,我们根据选择的格式调用相应的工厂方法

用户不关心产品及其相应的变体是如何创建的;他们只需要选择一个 主题格式 ,就这样 - 根据要求创建相应的产品。

客户端代码现在对于更改来说是稳健的。如果我们想添加新的主题或样式,我们只需创建一个新工厂来负责这样做。

我们使用 chalk 库根据终端日志的语义为它们着色。

为了能够从 CLI 应用程序的用户获取输入,我们使用了 inquirer 包,它提供了一种真正有吸引力且用户友好的方式来从用户获取各种类型的输入。

  1. getUserInput函数用于获取主要简历信息:姓名、电子邮件、电话。
  2. getExperience实用函数用于递归地检索用户的体验信息。换句话说,它会提示用户填写第一个条目的体验信息,然后询问他们是否有其他体验要添加。如果答案是否定的,该函数就返回;另一方面,如果他们选择“是”,他们将被要求再次填写下一次体验的信息。

utils/userInput

export abstract class PDFResume {}
export abstract class JSONResume {}
export abstract class MarkdownResume {}

结论

抽象工厂模式是软件设计人员和开发人员的强大工具。它提供了一种结构化方法来创建相关对象系列,而无需指定它们的具体类。此模式在以下情况下特别有用:

  1. 系统应该独立于其产品的创建、组成和表示方式。
  2. 系统需要配置多个产品系列之一。
  3. 一系列相关的产品对象被设计为一起使用,您需要强制执行此约束。
  4. 您想要提供产品的类库,并且您只想展示它们的接口,而不是它们的实现。

在我们的实际示例中,我们看到了如何应用抽象工厂模式来创建灵活且可扩展的简历生成系统。该系统可以轻松适应新的简历样式或输出格式,而无需修改现有代码,展示了开放/封闭原则在行动中的力量。

虽然抽象工厂模式提供了许多好处,但值得注意的是,它可能会给您的代码库带来额外的复杂性。因此,评估它提供的灵活性对于您的特定用例是否是必要的至关重要。

通过掌握抽象工厂等设计模式,您将能够更好地创建健壮、灵活且可维护的软件系统。在您的项目中不断探索和应用这些模式,以提高您的软件设计技能。

接触

如果您有任何疑问或想进一步讨论,请随时与我联系。

编码愉快!

以上是掌握抽象工厂模式:综合指南的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn