您是否曾經發現自己需要在應用程式中建立不同系列物件的多個變體,而無需一遍又一遍地重複邏輯?
或者也許您已經建立了一個應用程序,卻發現新的需求或客戶更改的偏好需要全新的對象,從而迫使您重新編寫整個程式碼庫?
如果有一種方法可以無縫引入新的變體而不破壞現有代碼只需插入新的實現會怎麼樣?
這就是抽象工廠設計模式的用武之地!
在本教程中,我們將透過建立 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中文網其他相關文章!