Heim >Web-Frontend >js-Tutorial >Beherrschen des Prototyp-Entwurfsmusters: Ein umfassender Leitfaden
Haben Sie schon einmal ein Objekt aus einer Bibliothek importiert und versucht, es zu klonen, was jedoch scheiterte, weil das Klonen umfangreiche Kenntnisse über die Interna der Bibliothek erfordert?
Oder vielleicht haben Sie, nachdem Sie lange an einem Projekt gearbeitet haben, eine Pause eingelegt, um Ihren Code umzugestalten, und festgestellt, dass Sie viele komplexe Objekte in verschiedenen Teilen Ihrer Codebasis neu klonen?
Nun, das Prototyp-Designmuster ist genau das Richtige für Sie!
In diesem Artikel werden wir das Prototyp-Entwurfsmuster untersuchen und gleichzeitig eine voll funktionsfähige Journaling-Vorlagen-Node.js-CLI-Anwendung erstellen.
Lassen Sie uns ohne weitere Umschweife darauf eingehen!
Prototyp ist ein kreatives Designmuster, eine Kategorie von Designmustern, die sich mit den verschiedenen Problemen befasst, die mit der nativen Art der Objekterstellung mit dem Neuen einhergehen Schlüsselwort oder Operator.
Das Fabrikdesignmuster löst die folgenden Erstellungsprobleme:
Wie können Sie ein vorhandenes Objekt in Ihrer Anwendung kopieren, ohne von seinen konkreten Klassen abhängig zu sein?
Einige komplexe Objekte sind schwer zu klonen, weil sie entweder viele Felder haben, die eine bestimmte Geschäftslogik erfordern, die Ihnen entweder nicht bekannt ist, oder weil sie viele private Felder haben, auf die von außen nicht zugegriffen werden kann die Objekte.
Nehmen wir als Beispiel das Socket-Objekt, das aus der socket.io-Bibliothek importiert wurde. Stellen Sie sich vor, dass Sie das selbst klonen müssen?
Sie müssen den Code in der Bibliothek durchgehen und verstehen, wie Sockets funktionieren. Die Objekte haben sogar einige zirkuläre Abhängigkeiten, mit denen Sie sich selbst befassen müssen, um sie zu klonen.
Darüber hinaus hängt Ihr Code von der Socket-Klasse oder -Schnittstelle und der entsprechenden Geschäftslogik ab, um ihn zu erstellen, was gegen das Prinzip der soliden Abhängigkeitsinversion verstößt und Ihren Code weniger robust gegenüber Änderungen macht.
Das Prototyp-Entwurfsmuster löst diese Probleme, indem es die Verantwortung für das Kopieren des Objekts in das Objekt selbst delegiert, indem in jeder Objektklasse eine Klonmethode deklariert wird, die es sein soll klonbar.
class Socket { // code........ clone(): Socket { // business logic to instantiate the socket. return new Socket(/*...Params*/) } } const socket1 = new Socket() const socket2 = socket1.clone()
Um das Prototyp-Entwurfsmuster zu implementieren, können Sie entweder die Methode Klonen direkt in das klonbare Objekt einbinden.
Oder erstellen Sie eine gemeinsame Schnittstelle Prototyp, die von allen klonbaren Objekten implementiert werden kann.
Ein Vorteil einer gemeinsamen Schnittstelle ist die Möglichkeit, alle Prototypen in einer gemeinsamen Registrierungsdienstklasse zu registrieren, die für die Zwischenspeicherung der häufig verwendeten Prototypen und deren Rückgabe an den Benutzer verantwortlich ist. Anstatt die Objekte jedes Mal klonen zu müssen, wenn die Methode clone aufgerufen wird.
Das kann besonders beim Klonen komplexer Objekte sehr praktisch sein.
In diesem Abschnitt demonstrieren wir dieses Entwurfsmuster, indem wir eine Nodejs-CLI-Anwendung für Mini-Journaling-Vorlagen erstellen.
Wie wir zuvor gesehen haben, delegiert das Prototyp-Entwurfsmuster die Verantwortung für das Klonen des Objekts in das Objekt selbst.
Aber haben Sie sich gefragt, warum es überhaupt Prototyp heißt?? Ich meine, was hat das mit Klonen zu tun?
Das werden wir anhand dieses praktischen Beispiels beantworten, lesen Sie weiter und bleiben Sie dran.
Den endgültigen Code finden Sie in diesem Repository. Klonen Sie es einfach und führen Sie die folgenden Befehle aus.
Erstellen wir zunächst eine JournalTemplate mit den folgenden Attributen:
Jeder Abschnitt besteht aus den folgenden Attributen:
JournalTemplate.ts
class Socket { // code........ clone(): Socket { // business logic to instantiate the socket. return new Socket(/*...Params*/) } } const socket1 = new Socket() const socket2 = socket1.clone()
Die Klasse JournalTemplate verfügt über viele Hilfsmethoden zum Festlegen ihrer verschiedenen Attribute.
Die Methode display wird später verwendet, um eine farbige, gut formatierte Ausgabe auf dem Terminal anzuzeigen.
Das Kreidepaket wird verwendet, um einige Teile für den ausgegebenen Terminaltext einzufärben.
Unsere JournalTemplate-Objekte sollen, wie der Name schon sagt, als Vorlagen oder Prototypen für die Erstellung anderer Vorlagen oder Journaldateieinträge verwendet werden.
Deshalb haben wir die Methode clone zur Klasse JournalTemplate hinzugefügt.
Wir haben es hinzugefügt, um die Verantwortung für die Handhabung der Klon-Geschäftslogik dem JournalTemplate-Objekt selbst und nicht dem verbrauchenden Code zu übertragen.
Jetzt erstellen wir unsere Klasse TemplateRegistry, die für die Speicherung der Prototypinstanzen der Klasse JournalTemplate verantwortlich ist. Gleichzeitig werden Methoden zur Manipulation dieser Instanzen bereitgestellt.
TemplateRegistry.ts
class Socket { // code........ clone(): Socket { // business logic to instantiate the socket. return new Socket(/*...Params*/) } } const socket1 = new Socket() const socket2 = socket1.clone()
Das Register speichert diese Klassen in einem Map-Objekt, um sie schnell nach Namen abrufen zu können, und stellt viele Hilfsmethoden zum Hinzufügen oder Entfernen von Vorlageninstanzen bereit.
Jetzt instanziieren wir das Vorlagenregister und setzen dann einige anfängliche Vorlagen ein.
registry.ts
import chalk from "chalk" import { TemplateSection } from "./types" export interface TemplateSection { title: string prompt: string } export class JournalTemplate { constructor( public name: string, public sections: TemplateSection[] ) {} clone(): JournalTemplate { return new JournalTemplate( this.name, this.sections.map((s) => ({ ...s })) ) } display(): void { console.log(chalk.cyan(`\nTemplate: ${this.name}`)) this.sections.forEach((section, index) => { console.log(chalk.yellow(`${index + 1}. ${section.title}`)) console.log(chalk.gray(` Prompt: ${section.prompt}`)) }) } addSection(section: TemplateSection): void { this.sections.push(section) } removeSection(index: number): void { if (index >= 0 && index < this.sections.length) { this.sections.splice(index, 1) } else { throw new Error("Invalid section index") } } editSection(index: number, newSection: TemplateSection): void { if (index >= 0 && index < this.sections.length) { this.sections[index] = newSection } else { throw new Error("Invalid section index") } } getSectionCount(): number { return this.sections.length } getSection(index: number): TemplateSection | undefined { return this.sections[index] } setName(newName: string): void { this.name = newName } }
In diesem Abschnitt definieren wir eine Reihe von Funktionen, die in unserem Anwendungsmenü verwendet werden, um verschiedene Aktionen auszuführen, wie zum Beispiel:
Die neu erstellten Vorlagen können zum Erstellen neuer Journaleinträge (1) verwendet werden.
Erstellen Sie eine Vorlage :
TemplateActions.ts > createTemplate
import { JournalTemplate } from "./JournalTemplate" export class TemplateRegistry { private templates: Map<string, JournalTemplate> = new Map() addTemplate(name: string, template: JournalTemplate): void { this.templates.set(name, template) } getTemplate(name: string): JournalTemplate | undefined { const template = this.templates.get(name) return template ? template.clone() : undefined } getTemplateNames(): string[] { return Array.from(this.templates.keys()) } }
- Um eine Vorlage zu erstellen, fordern wir den Benutzer zunächst auf, einen Vorlagennamen einzugeben.
- Dann instanziieren wir ein neues Vorlagenobjekt mit dem Namen und einem leeren Array für die Abschnitte.
- Danach fordern wir den Benutzer auf, die Details der Abschnitte einzugeben. Nachdem er die Informationen zu jedem Abschnitt eingegeben hat, kann der Benutzer entweder anhalten oder weitere Abschnitte eingeben.
utils.ts > promptForSectionDetails
import { JournalTemplate } from "./JournalTemplate" import { TemplateRegistry } from "./TemplateRegistry" export const registry = new TemplateRegistry() registry.addTemplate( "Daily Reflection", new JournalTemplate("Daily Reflection", [ { title: "Gratitude", prompt: "List three things you're grateful for today.", }, { title: "Accomplishments", prompt: "What did you accomplish today?" }, { title: "Challenges", prompt: "What challenges did you face and how did you overcome them?", }, { title: "Tomorrow's Goals", prompt: "What are your top 3 priorities for tomorrow?", }, ]) ) registry.addTemplate( "Weekly Review", new JournalTemplate("Weekly Review", [ { title: "Highlights", prompt: "What were the highlights of your week?" }, { title: "Lessons Learned", prompt: "What important lessons did you learn this week?", }, { title: "Progress on Goals", prompt: "How did you progress towards your goals this week?", }, { title: "Next Week's Focus", prompt: "What's your main focus for next week?", }, ]) )Die Funktion promptForSectionDetails verwendet das Paket inquirer, um nach dem Titel zu fragen und dann den Benutzer nacheinander zu fragen.
Vorlagen anzeigen :
TemplateActions.ts > viewTemplates
import chalk from "chalk" import inquirer from "inquirer" import { JournalTemplate } from "./JournalTemplate" import { registry } from "./registry" import { editTemplateSections } from "./templateSectionsActions" import { promptForSectionDetails } from "./utils" export async function createTemplate(): Promise<void> { const { name } = await inquirer.prompt<{ name: string }>([ { type: "input", name: "name", message: "Enter a name for the new template:", }, ]) const newTemplate = new JournalTemplate(name, []) let addMore = true while (addMore) { const newSection = await promptForSectionDetails() newTemplate.addSection(newSection) const { more } = await inquirer.prompt<{ more: boolean }>([ { type: "confirm", name: "more", message: "Add another section?", default: false, }, ]) addMore = more } registry.addTemplate(name, newTemplate) console.log(chalk.green(`Template "${name}" created successfully!`)) }Die Funktion viewTemplates funktioniert wie folgt:
- Wir holen uns zuerst alle Vorlagen aus der Registrierung, dann durchlaufen wir das zurückgegebene Vorlagenarray und verwenden die Methode display, die wir zuvor in der JournalTemplate definiert haben Klasse.
Verwenden Sie eine Vorlage, um einen Journaleintrag zu erstellen: Der Grund für die Erstellung von Journaling-Vorlagen besteht darin, uns das Leben beim Schreiben unserer verschiedenen Arten von Journalen zu erleichtern, anstatt vor einer leeren Seite zu stehen, ist es besser, einfacher zu sein Füllen Sie das Tagebuch, wenn Sie mit einer Reihe aufeinanderfolgender Abschnittstitel und Eingabeaufforderungen konfrontiert werden.
Lass uns in die useTemplate-Funktion eintauchen:
- Zuerst wählen wir eine Vorlage aus den vorhandenen Vorlagen aus, nachdem wir die Vorlagennamen aus dem Register erhalten haben.
- Für jeden Abschnitt in der Vorlage wird der Benutzer aufgefordert, seinen bevorzugten Editor zu öffnen, um den Text des Zeitschriftenabschnitts auszufüllen.
TemplateActions.ts > useTemplate
class Socket { // code........ clone(): Socket { // business logic to instantiate the socket. return new Socket(/*...Params*/) } } const socket1 = new Socket() const socket2 = socket1.clone()Erstellen Sie eine Vorlage aus einer vorhandenen Vorlage :
Schließlich werden wir das Prototyp-Entwurfsmuster in Aktion sehen.
Lassen Sie uns untersuchen, wie wir neue Arten von Vorlagen dynamisch erstellen können, indem wir die vorhandenen Vorlagen überschreiben.
- Zuerst fordern wir den Benutzer auf, die Vorlage auszuwählen, die er von den vorhandenen überschreiben möchte.
- Dann fordern wir Sie erneut auf, den Namen der neu erstellten Vorlage einzugeben.
- Wir verwenden die Registrierung, um die Vorlage mit dem vom Benutzer ausgewählten Vorlagennamen zu erhalten.
- Wir verwenden die Methode Klonen, um ein Klonobjekt zu erhalten, das der ausgewählten Vorlage entspricht.
Wie Sie dem Code unten entnehmen können, müssen wir nicht einmal die Details der JournalTemplate-Klasse kennen oder unseren Code durch den Import verunreinigen.
TemplateActions.ts > createFromExistingTemplate
- Schließlich legen wir den vom Benutzer angegebenen Vorlagennamen für das neu erstellte Objekt fest und fordern den Benutzer dann auf, mithilfe der Methode editTemplateSections alle Rohoperationen an den vorhandenen Vorlagenabschnitten durchzuführen, was wir tun werden Erläuterung unten direkt nach dem Codeblock.
import chalk from "chalk" import { TemplateSection } from "./types" export interface TemplateSection { title: string prompt: string } export class JournalTemplate { constructor( public name: string, public sections: TemplateSection[] ) {} clone(): JournalTemplate { return new JournalTemplate( this.name, this.sections.map((s) => ({ ...s })) ) } display(): void { console.log(chalk.cyan(`\nTemplate: ${this.name}`)) this.sections.forEach((section, index) => { console.log(chalk.yellow(`${index + 1}. ${section.title}`)) console.log(chalk.gray(` Prompt: ${section.prompt}`)) }) } addSection(section: TemplateSection): void { this.sections.push(section) } removeSection(index: number): void { if (index >= 0 && index < this.sections.length) { this.sections.splice(index, 1) } else { throw new Error("Invalid section index") } } editSection(index: number, newSection: TemplateSection): void { if (index >= 0 && index < this.sections.length) { this.sections[index] = newSection } else { throw new Error("Invalid section index") } } getSectionCount(): number { return this.sections.length } getSection(index: number): TemplateSection | undefined { return this.sections[index] } setName(newName: string): void { this.name = newName } }templateSectionsAction > editTemplateSections
import { JournalTemplate } from "./JournalTemplate" export class TemplateRegistry { private templates: Map<string, JournalTemplate> = new Map() addTemplate(name: string, template: JournalTemplate): void { this.templates.set(name, template) } getTemplate(name: string): JournalTemplate | undefined { const template = this.templates.get(name) return template ? template.clone() : undefined } getTemplateNames(): string[] { return Array.from(this.templates.keys()) } }Die unten definierten editTemplateSections-Eingabeaufforderungen zeigen grundsätzlich ein Menü an, in dem der Benutzer aufgefordert wird, die vorhandenen Abschnitte nach Bedarf zu überschreiben, indem er verschiedene Vorgänge anbietet, wie zum Beispiel:
- Abschnitt hinzufügen
- Abschnitt entfernen
- Abschnitt bearbeiten
Anwendungsmenü
Schließlich nutzen wir alle vorherigen Funktionen in unserer Datei index.ts, die die CLI-App bootet und ein Menü mit den verschiedenen Optionen zur Vorlagenbearbeitung anzeigt:
- Erstellen Sie eine Vorlage.
- Erstellen Sie eine Vorlage aus einer vorhandenen Vorlage.
- Vorlagen anzeigen.
- Verwenden Sie eine Vorlage, um einen Journaleintrag zu erstellen.
- Beenden Sie das Programm.
index.ts
class Socket { // code........ clone(): Socket { // business logic to instantiate the socket. return new Socket(/*...Params*/) } } const socket1 = new Socket() const socket2 = socket1.clone()Abschluss
Das Prototyp-Entwurfsmuster bietet eine leistungsstarke Möglichkeit, neue Objekte durch Klonen vorhandener Objekte zu erstellen. In unserer Journaling-Vorlagenanwendung haben wir gesehen, wie dieses Muster es uns ermöglicht, neue Vorlagen basierend auf vorhandenen zu erstellen, was die Flexibilität und Effizienz des Prototype-Musters demonstriert.
Durch die Verwendung dieses Musters haben wir ein System erstellt, das einfach zu erweitern und zu ändern ist und die wahre Leistungsfähigkeit objektorientierter Entwurfsmuster in realen Anwendungen demonstriert.
Kontakt
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 vonBeherrschen des Prototyp-Entwurfsmusters: Ein umfassender Leitfaden. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!