Müssen Sie in Ihrer Anwendung mehrere Variationen verschiedener Objektfamilien erstellen, ohne die Logik immer wieder zu duplizieren?
Oder vielleicht haben Sie eine Anwendung erstellt, nur um dann festzustellen, dass neue Anforderungen oder geänderte Vorlieben eines Kunden völlig neue Objekte erfordern, was Sie dazu zwingt, Ihre gesamte Codebasis zu überarbeiten?
Was wäre, wenn es eine Möglichkeit gäbe, nahtlos neue Variationen einzuführen, ohne Ihren vorhandenen Code zu beschädigen, indem Sie einfach eine neue Implementierung einbinden?
Hier kommt das Designmuster Abstract Factory ins Spiel!
In diesem Tutorial werden wir dieses leistungsstarke Designmuster aufschlüsseln, indem wir eine Node.js-CLI-Anwendung zum Erstellen mehrerer Arten von Lebensläufen erstellen, die mehrere Formate und Themen unterstützen.
Die Abstract Factory ist ein kreatives Designmuster, eine Kategorie von Designmustern, die sich mit den verschiedenen Problemen befasst, die mit der nativen Art der Objekterstellung mithilfe der neues Schlüsselwort oder Operator.
Sie können sich dasProblemAbstrakte Fabrik-Entwurfsmuster als eine Verallgemeinerung des Fabrikmethoden-Entwurfsmusters vorstellen, das wir in diesem Blogartikel behandelt haben.
Abstract Factory löst die folgenden Probleme:
Abstract Factory löst diese Probleme, indem es für jeden Produkttyp eine Schnittstelle oder abstrakte Klasse deklariert.
export abstract class PDFResume {} export abstract class JSONResume {} export abstract class MarkdownResume {}
abstrakte Fabrik, eine Schnittstelle, die Fabrikmethoden deklariert, die jeden Produkttyp erstellen:
export interface ResumeFactory { createPDFResume(): PDFResume createMarkdownResume(): MarkdownResume createJSONResume(): JSONResume }
Die Antwort besteht darin, eine ConcreteFactory zu erstellen, die die abstrakte Fabrik ( ResumeFactory ) implementiert.
export abstract class PDFResume {} export abstract class JSONResume {} export abstract class MarkdownResume {}
Um nun unsere Fabriken in unserer Client-Klasse zu nutzen, müssen wir nur noch eine Variable vom Typ ResumeFactory deklarieren und dann abhängig von der Benutzereingabe die entsprechende Betonfabrik instanziieren.
Kundencode:
export interface ResumeFactory { createPDFResume(): PDFResume createMarkdownResume(): MarkdownResume createJSONResume(): JSONResume }
Die Struktur des Abstract Factory-Entwurfsmusters besteht aus den folgenden Klassen:
In unserem Fall sind die in Factory deklarierten Factory-Methoden: createProductA und createProductB
ConcretProductA1 und ConcretProductA2 implementieren IProductA. ConcretProductB1 und ConcretProductB2 implementieren IProductB.
In diesem Abschnitt werden wir das vorherige Beispiel in die Tat umsetzen, indem wir eine voll funktionsfähige Node.js TypeScript CLI-Anwendung erstellen, die einen Lebenslauf basierend auf dem vom Benutzer gewählten Thema und Format erstellt.
Sie können sich gerne den vollständigen Arbeitscode ansehen, indem Sie dieses Repository auf Ihrem Computer klonen.
Führen Sie dann die folgenden Befehle aus:
export abstract class PDFResume {} export abstract class JSONResume {} export abstract class MarkdownResume {}
Beginnen wir mit der Deklaration der Typen, die wir im gesamten Tutorial verwenden werden, um die Typsicherheit zu gewährleisten.
Schnittstellen/Typen
export interface ResumeFactory { createPDFResume(): PDFResume createMarkdownResume(): MarkdownResume createJSONResume(): JSONResume }
Jetzt deklarieren wir den Typ generische Fabrik, der die drei Fabrikmethoden definiert, die den verschiedenen unterstützten Produkttypen entsprechen: PDFResume , MarkdownResume , und JSONResume.
Schnittstellen/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 } }
Wir werden ihren Code im nächsten Abschnitt durchgehen.
Als Nächstes beginnen wir mit der Erstellung unserer generischen Produktklassen.
Jeder Produkttyp wird eine abstrakte Klasse sein, da wir sowohl Attribute als auch Methoden zwischen den entsprechenden Untertypen teilen möchten.
Die Klasse definiert:
resumes/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.") }
Das Schlüsselwort abstract bedeutet, dass die Klasse ein generischer Typ ist, der nicht instanziiert werden kann; Es kann nur von anderen Klassen geerbt werden.
Die Klasse definiert:
Lebensläufe/Markdown/MarkdownResume
export abstract class PDFResume {} export abstract class JSONResume {} export abstract class MarkdownResume {}
Die Klasse verfügt über ein geschütztes doc-Objekt vom Typ PDFKit.PDFDocument , das aus einer Bibliothek namens pdfkit importiert wird. Die Bibliothek vereinfacht das Erstellen und Bearbeiten von PDF-Dokumenten durch ihre objektorientierte Schnittstelle.
Die Klasse definiert:
Lebensläufe/pdf/PDFResume
export interface ResumeFactory { createPDFResume(): PDFResume createMarkdownResume(): MarkdownResume createJSONResume(): JSONResume }
Nachdem wir nun unsere generischen Produkttypen und unsere abstrakte Fabrik definiert haben, ist es an der Zeit, mit der Erstellung unserer ConcreteFactories fortzufahren, die dem entsprechen verschiedene Varianten jedes generischen Produkttyps.
Wir haben 3 mögliche Varianten für einen Lebenslauf: Kreativ, Minimalistisch und Modern. Und 3 Arten von generischen Produkten: JSON , PDF und Markdown.
Die abstrakte Fabrik ( ResumeFactory ) definiert die 3 Fabrikmethoden, die für die Erstellung unserer Produkte verantwortlich sind:
Um mehrere Varianten pro Produkt zu unterstützen, müssen wir drei Betonfabriken erstellen.
Jede Betonfabrik wird die drei Arten von Produkten herstellen, jedoch mit ihren eigenen Geschmacksrichtungen:
Fabriken/CreativeResumeFactory
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 } }
Fabriken/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.") }
Fabriken/ModernResumeFactory
export abstract class PDFResume {} export abstract class JSONResume {} export abstract class MarkdownResume {}
Jetzt erstellen wir die vorherigen ConcreteProducts, die von der CreativeResumeFactory
zurückgegeben werdenPDF-Lebenslauf :
resumes/pdf/CreativePDFResume
export interface ResumeFactory { createPDFResume(): PDFResume createMarkdownResume(): MarkdownResume createJSONResume(): JSONResume }
Markdown-Lebenslauf :
Lebensläufe/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-Lebenslauf :
resumes/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.") }
Als nächstes erstellen wir die vorherigen ConcreteProducts, die von der MinimalistResumeFactory
zurückgegeben werdenPDF-Lebenslauf :
resumes/pdf/MinimalistPDFResume
npm install npm start
Markdown-Lebenslauf :
Lebensläufe/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-Lebenslauf :
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 }
Schließlich erstellen wir die vorherigen ConcreteProducts, die von der ModernResumeFactory
zurückgegeben werdenPDF-Lebenslauf :
resumes/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-Lebenslauf :
Lebensläufe/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-Lebenslauf :
resumes/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 } }
Lassen Sie uns beginnen, die Früchte unserer bisherigen Arbeit zu tragen, indem wir unsere Fabriken im Kundencode verwenden.
Schauen Sie sich an, wie wir unsere Lebenslauf-Builder-Bibliothek jetzt auf sehr saubere Weise nutzen können, indem wir einfach unsere Fabriken nutzen.
Der Benutzer muss nur zwei Dinge angeben:
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 } }
Der obige Code funktioniert in drei Schritten:
Dem Benutzer ist es egal, wie Produkte und die entsprechenden Varianten erstellt werden; Sie müssen nur ein Thema und ein Format auswählen, und schon wird das entsprechende Produkt wie gewünscht erstellt.
Der Client-Code ist jetzt robust gegenüber Änderungen. Wenn wir ein neues Thema oder einen neuen Stil hinzufügen möchten, können wir einfach eine neue Fabrik erstellen, die dafür verantwortlich ist.
Wir haben die Kreidebibliothek verwendet, um unsere Terminalprotokolle entsprechend ihrer semantischen Bedeutung einzufärben.
Um die Eingaben vom Benutzer der CLI-App erhalten zu können, haben wir das Paket inquirer verwendet, das eine wirklich ansprechende und benutzerfreundliche Möglichkeit bietet, verschiedene Arten von Eingaben vom Benutzer zu erhalten.
utils/userInput
export abstract class PDFResume {} export abstract class JSONResume {} export abstract class MarkdownResume {}
Das Abstract Factory-Muster ist ein leistungsstarkes Werkzeug im Arsenal von Softwaredesignern und -entwicklern. Es bietet einen strukturierten Ansatz zum Erstellen von Familien verwandter Objekte, ohne deren konkrete Klassen anzugeben. Dieses Muster ist besonders nützlich, wenn:
In unserem praktischen Beispiel haben wir gesehen, wie das Abstract Factory-Muster angewendet werden kann, um ein flexibles und erweiterbares System zur Lebenslauferstellung zu erstellen. Dieses System kann problemlos neue Lebenslaufstile oder Ausgabeformate integrieren, ohne den vorhandenen Code zu ändern, und demonstriert so die Leistungsfähigkeit des Offen/Geschlossen-Prinzips in Aktion.
Obwohl das Abstract Factory-Muster viele Vorteile bietet, ist es wichtig zu beachten, dass es Ihrer Codebasis zusätzliche Komplexität verleihen kann. Daher ist es wichtig zu beurteilen, ob die dadurch gebotene Flexibilität für Ihren spezifischen Anwendungsfall erforderlich ist.
Durch die Beherrschung von Entwurfsmustern wie der Abstract Factory sind Sie besser für die Erstellung robuster, flexibler und wartbarer Softwaresysteme gerüstet. Erforschen Sie diese Muster weiter und wenden Sie sie in Ihren Projekten an, um Ihre Software-Designfähigkeiten zu verbessern.
Wenn Sie Fragen haben oder etwas weiter besprechen möchten, können Sie mich gerne hier kontaktieren.
Viel Spaß beim Codieren!
Das obige ist der detaillierte Inhalt vonDas abstrakte Fabrikmuster beherrschen: Ein umfassender Leitfaden. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!