您是否曾经发现自己需要在应用程序中创建不同系列对象的多个变体,而无需一遍又一遍地重复逻辑?
或者也许您已经构建了一个应用程序,却发现新的需求或客户更改的偏好需要全新的对象,从而迫使您重新编写整个代码库?
如果有一种方法可以无缝引入新的变体而不破坏现有代码只需插入新的实现会怎么样?
这就是抽象工厂设计模式的用武之地!
在本教程中,我们将通过构建 Node.js CLI 应用程序来分解这种强大的设计模式,用于创建支持多种格式和主题的多种类型的简历。
抽象工厂是一种创意设计模式,它是一类设计模式,用于处理使用新关键字或运算符。
您可以将问题抽象工厂设计模式视为我们在本博客文章中介绍的工厂方法设计模式的概括。
抽象工厂设计模式解决了以下问题:
抽象工厂设计模式通过为每种类型的产品声明一个接口或抽象类来解决这些问题。
export abstract class PDFResume {} export abstract class JSONResume {} export abstract class MarkdownResume {}然后,正如模式名称所暗示的那样,我们创建一个
抽象工厂 ,它是一个声明 工厂方法 的接口,用于创建每种类型的产品:
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 }
抽象工厂设计模式的结构由以下类组成:
在我们的例子中,Factory 中声明的工厂方法是:createProductA 和 createProductB
ConcretProductA1 和 ConcretProductA2 实现 IProductA ConcretProductB1 和 ConcretProductB2 实现 IProductB
在本节中,我们将通过构建一个完全工作的 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 }
现在,让我们声明 通用工厂 类型,它将定义三个 工厂方法,它们对应于不同支持的产品类型: PDFResume 、 MarkdownResume ,以及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 } }
我们将在下一节中详细介绍他们的代码。
接下来,让我们继续创建通用产品类。
每个产品类型都将是一个抽象类,因为我们希望在其相应的子类型之间共享属性和方法。
该类定义:
简历/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关键字表示该类是泛型类型,无法实例化;它只能被其他类继承。
该类定义:
简历/markdown/MarkdownResume
export abstract class PDFResume {} export abstract class JSONResume {} export abstract class MarkdownResume {}
该类有一个类型为 PDFKit.PDFDocument 的受保护 doc 对象,该对象是从名为 pdfkit 的库导入的。该库通过其面向对象的界面简化了 PDF 文档的创建和操作。
该类定义:
简历/pdf/PDFResume
export interface ResumeFactory { createPDFResume(): PDFResume createMarkdownResume(): MarkdownResume createJSONResume(): JSONResume }
现在我们已经定义了 通用产品类型 和 抽象工厂 ,是时候继续创建 ConcreteFactories 了,它们对应于每种通用产品类型的不同变体。
我们有 3 种可能的简历变体:创意、极简和现代。以及 3 种通用产品: JSON 、 PDF 和 Markdown.
抽象工厂 (ResumeFactory) 定义了 3 个工厂方法,负责创建我们的产品:
为了支持每个产品的多个变体,我们必须创建 3 个具体工厂。
每个混凝土工厂将生产3种类型的产品,但具有自己的风味:
工厂/创意简历工厂
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 } }
工厂/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.") }
工厂/ModernResumeFactory
export abstract class PDFResume {} export abstract class JSONResume {} export abstract class MarkdownResume {}
现在,让我们创建之前的 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
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 } }
上面的代码分三步运行:
用户不关心产品及其相应的变体是如何创建的;他们只需要选择一个 主题 和 格式 ,就这样 - 根据要求创建相应的产品。
客户端代码现在对于更改来说是稳健的。如果我们想添加新的主题或样式,我们只需创建一个新工厂来负责这样做。
我们使用 chalk 库根据终端日志的语义为它们着色。
为了能够从 CLI 应用程序的用户获取输入,我们使用了 inquirer 包,它提供了一种真正有吸引力且用户友好的方式来从用户获取各种类型的输入。
utils/userInput
export abstract class PDFResume {} export abstract class JSONResume {} export abstract class MarkdownResume {}
抽象工厂模式是软件设计人员和开发人员的强大工具。它提供了一种结构化方法来创建相关对象系列,而无需指定它们的具体类。此模式在以下情况下特别有用:
在我们的实际示例中,我们看到了如何应用抽象工厂模式来创建灵活且可扩展的简历生成系统。该系统可以轻松适应新的简历样式或输出格式,而无需修改现有代码,展示了开放/封闭原则在行动中的力量。
虽然抽象工厂模式提供了许多好处,但值得注意的是,它可能会给您的代码库带来额外的复杂性。因此,评估它提供的灵活性对于您的特定用例是否是必要的至关重要。
通过掌握抽象工厂等设计模式,您将能够更好地创建健壮、灵活且可维护的软件系统。在您的项目中不断探索和应用这些模式,以提高您的软件设计技能。
如果您有任何疑问或想进一步讨论,请随时与我联系。
编码愉快!
以上是掌握抽象工厂模式:综合指南的详细内容。更多信息请关注PHP中文网其他相关文章!