首頁 > web前端 > js教程 > 掌握抽象工廠模式:綜合指南

掌握抽象工廠模式:綜合指南

Susan Sarandon
發布: 2024-12-05 20:41:14
原創
900 人瀏覽過

您是否曾經發現自己需要在應用程式中建立不同系列物件的多個變體,而無需一遍又一遍地重複邏輯?

或者也許您已經建立了一個應用程序,卻發現新的需求或客戶更改的偏好需要全新的對象,從而迫使您重新編寫整個程式碼庫?

如果有一種方法可以無縫引入新的變體而不破壞現有代碼只需插入新的實現會怎麼樣?

這就是抽象工廠設計模式的用武之地!

在本教程中,我們將透過建立 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中文網其他相關文章!

來源:dev.to
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板