Avez-vous déjà importé un objet depuis une bibliothèque et essayé de le cloner, mais sans succès, car le clonage nécessite une connaissance approfondie des composants internes de la bibliothèque ?
Ou peut-être qu'après avoir travaillé longtemps sur un projet, vous avez pris une pause pour refactoriser votre code et remarqué que vous reclonez de nombreux objets complexes dans diverses parties de votre base de code ?
Eh bien, le modèle de conception du prototype est là pour vous !
Dans cet article, nous explorerons le modèle de conception du prototype tout en créant une application CLI Node.js de modèles de journalisation entièrement fonctionnelle.
Sans plus tarder, plongeons-y !
Prototype est un modèle de conception créatif, qui est une catégorie de modèles de conception qui traite des différents problèmes liés à la manière native de créer des objets avec le nouveau mot-clé ou opérateur.
Le modèle de conception d'usine résout les problèmes de création suivants :
Comment copier un objet existant dans votre application sans dépendre de ses classes concrètes ?
Certains objets complexes sont difficiles à cloner, car soit ils ont beaucoup de champs qui nécessitent une logique métier particulière qui soit soit vous ne la connaissez pas, soit ils ont beaucoup de champs privés qui ne sont pas accessibles de l'extérieur. les objets.
Prenons comme exemple l'objet socket importé de la bibliothèque socket.io, imaginant devoir le cloner vous-même ?
Vous devrez parcourir son code à l'intérieur de la bibliothèque, comprendre comment fonctionnent les sockets, les objets ont même des dépendances circulaires que vous devrez gérer vous-même afin de le cloner.
En plus de cela, votre code dépendra de la classe ou de l'interface de socket et de la logique métier correspondante pour la créer, ce qui viole le solide principe d'inversion des dépendances et rend votre code moins robuste aux modifications.
Le modèle de conception de prototype résout ces problèmes, en déléguant la responsabilité de copier l'objet dans l'objet lui-même, en déclarant une méthode clone dans la classe de chaque objet qui est censée être clonable.
class Socket { // code........ clone(): Socket { // business logic to instantiate the socket. return new Socket(/*...Params*/) } } const socket1 = new Socket() const socket2 = socket1.clone()
Pour implémenter le modèle de conception du prototype, vous pouvez soit inclure directement la méthode clone à l'intérieur de l'objet clonable.
Ou créez une interface commune Prototype qui peut être implémentée par tous les objets clonables.
L'un des avantages d'avoir une interface commune est la possibilité d'enregistrer tous les prototypes, dans une classe de service de registre commune, qui sera chargée de mettre en cache les prototypes fréquemment utilisés et de les renvoyer à l'utilisateur. Au lieu d'avoir à cloner les objets à chaque fois que la méthode clone est appelée.
Cela peut être très pratique, surtout lors du clonage d'objets complexes.
Dans cette section, nous allons faire une démonstration de ce modèle de conception en créant une application CLI Nodejs de mini modèles de journalisation.
Comme nous l'avons vu précédemment, le modèle de conception du prototype délègue la responsabilité du clonage de l'objet dans l'objet lui-même.
Mais vous êtes-vous demandé pourquoi on l'appelle même prototype ? Je veux dire, qu'est-ce que cela a à voir avec le clonage ?
Nous y répondrons à travers cet exemple pratique, continuez à lire et restez à l'écoute.
Vous pouvez trouver le code final dans ce référentiel. Clonez-le simplement et exécutez les commandes suivantes.
Créons d'abord un JournalTemplate qui a les attributs suivants :
Chaque section est composée des attributs suivants :
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()
La classe JournalTemplate possède de nombreuses méthodes utilitaires pour définir ses différents attributs.
La méthode display sera utilisée plus tard pour afficher une sortie colorée bien formatée au terminal.
le paquet de craie est utilisé pour colorer certains éléments du texte du terminal généré.
Nos objets JournalTemplate sont destinés à être utilisés, comme leur nom l'indique, comme modèles ou prototypes pour créer d'autres modèles ou entrées de fichiers de journalisation.
C'est pourquoi nous avons ajouté la méthode clone à la classe JournalTemplate.
Nous l'avons ajouté pour donner la responsabilité de gérer la logique métier de clonage à l'objet JournalTemplate lui-même plutôt qu'au code consommateur.
Créons maintenant notre classe TemplateRegistry, qui sera responsable du stockage des instances prototypes de la classe JournalTemplate. Tout en fournissant des méthodes pour manipuler ces instances.
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()
Le registre stocke ces classes dans un objet Map, pour une récupération rapide par nom, et expose de nombreuses méthodes utilitaires pour ajouter ou supprimer des instances de modèles.
Maintenant, instancions le registre de modèles, puis générons quelques modèles initiaux.
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 } }
Dans cette section, nous définirons un ensemble de fonctions qui seront utilisées dans notre menu d'application, pour exécuter diverses actions comme :
Les modèles nouvellement créés peuvent être utilisés pour créer de nouvelles entrées de journal (1).
Créer un modèle :
TemplateActions.ts > créer un modèle
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()) } }Copier après la connexionCopier après la connexion
- Pour créer un modèle, nous invitons d'abord l'utilisateur à saisir un nom de modèle.
- Ensuite, nous instancions un nouvel objet modèle, avec le nom et un tableau vide pour les sections.
- Après cela, nous invitons l'utilisateur à saisir les détails des sections, après avoir saisi les informations de chaque section, l'utilisateur peut choisir d'arrêter ou d'entrer plus de sections.
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?", }, ]) )Copier après la connexionLa fonction promptForSectionDetails utilise le package inquirer pour demander le titre, puis demande séquentiellement à l'utilisateur.
Voir les modèles :
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!`)) }Copier après la connexionLa fonction viewTemplates fonctionne comme suit :
- Nous récupérons d'abord tous les modèles du registre, puis nous parcourons le tableau de modèles renvoyé et utilisons la méthode display que nous avons définie plus tôt dans le JournalTemplate cours.
Utiliser un modèle pour créer une entrée de journal : La raison de la création de modèles de journalisation est de nous faciliter la vie lors de la rédaction de nos différents types de journaux, au lieu de faire face à une page vide, il est préférable de remplissez le journal lorsque vous êtes confronté à un tas de titres et d'invites de sections séquentielles.
Plongeons dans la fonction useTemplate :
- Nous sélectionnons d'abord un modèle parmi les modèles existants, après avoir obtenu les noms des modèles du registre.
- Pour chaque section du modèle, l'utilisateur sera invité à ouvrir son éditeur préféré pour remplir le texte de la section du journal.
TemplateActions.ts > utiliserModèle
class Socket { // code........ clone(): Socket { // business logic to instantiate the socket. return new Socket(/*...Params*/) } } const socket1 = new Socket() const socket2 = socket1.clone()Copier après la connexionCopier après la connexionCopier après la connexionCopier après la connexionCopier après la connexionCréer un modèle à partir d'un modèle existant :
Enfin, nous allons voir le modèle de conception du prototype en action.
Explorons comment créer de nouveaux types de modèles dynamiquement en remplaçant les modèles existants.
- Nous invitons d'abord l'utilisateur à sélectionner le modèle qu'il souhaite remplacer parmi ceux existants.
- Ensuite, nous lui demandons à nouveau de saisir le nom du modèle nouvellement créé.
- Nous utilisons le registre pour obtenir le modèle en fonction du nom du modèle sélectionné par l'utilisateur.
- Nous utilisons la méthode clone pour obtenir un objet clone qui correspond au modèle sélectionné.
Comme vous pouvez le voir dans le code ci-dessous, nous n'avons même pas besoin de connaître les détails de la classe JournalTemplate ou de polluer notre code en l'important.
TemplateActions.ts > createFromExistingTemplate
- Enfin, nous définissons le nom du modèle donné par l'utilisateur à l'objet nouvellement créé, puis invitons l'utilisateur à effectuer des opérations grossières sur les sections du modèle existantes à l'aide de la méthode editTemplateSections, que nous allons expliquant ci-dessous juste après le bloc de code.
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 } }Copier après la connexionCopier après la connexiontemplateSectionsAction > 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()) } }Copier après la connexionCopier après la connexionLes editTemplateSections définies ci-dessous affichent essentiellement un menu, demandant à l'utilisateur de remplacer les sections existantes selon ses besoins en proposant différentes opérations telles que :
- Ajouter une section
- Supprimer la section
- Modifier la section
Menu des applications
Enfin, nous utilisons toutes les fonctions précédentes dans notre fichier index.ts, qui démarre l'application cli et affiche un menu avec les différentes options de manipulation du modèle :
- Créez un modèle.
- Créez un modèle à partir d'un modèle existant.
- Afficher les modèles.
- Utilisez un modèle pour créer une entrée de journal.
- Quittez le programme.
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()Copier après la connexionCopier après la connexionCopier après la connexionCopier après la connexionCopier après la connexionConclusion
Le modèle de conception Prototype offre un moyen puissant de créer de nouveaux objets en clonant des objets existants. Dans notre application de modèles de journalisation, nous avons vu comment ce modèle nous permet de créer de nouveaux modèles basés sur ceux existants, démontrant la flexibilité et l'efficacité du modèle Prototype.
En utilisant ce modèle, nous avons créé un système facile à étendre et à modifier, mettant en valeur la véritable puissance des modèles de conception orientés objet dans les applications du monde réel.
Contact
Si vous avez des questions ou souhaitez discuter davantage de quelque chose, n'hésitez pas à me contacter ici.
Bon codage !
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!