Maison > interface Web > js tutoriel > Maîtriser le modèle d'usine abstraite : un guide complet

Maîtriser le modèle d'usine abstraite : un guide complet

Susan Sarandon
Libérer: 2024-12-05 20:41:14
original
901 Les gens l'ont consulté

Avez-vous déjà eu besoin de créer plusieurs variantes de différentes familles d'objets dans votre application sans dupliquer la logique encore et encore ?

Ou peut-être avez-vous créé une application pour vous rendre compte que de nouvelles exigences ou les préférences modifiées d'un client exigent des objets entièrement nouveaux, vous obligeant à retravailler l'intégralité de votre base de code ?

Et s'il existait un moyen d'introduire de nouvelles variantes de manière transparente sans casser votre code existant simplement en branchant une nouvelle implémentation ?

C’est là qu’intervient le modèle de conception Abstract Factory !

Dans ce didacticiel, nous allons décomposer ce modèle de conception puissant en créant une application CLI Node.js pour créer plusieurs types de CV prenant en charge plusieurs formats et thèmes.

Aperçu

La Abstract Factory 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 à l'aide du nouveau mot-clé ou opérateur.

Vous pouvez considérer le modèle de conception Abstract Factory comme une généralisation du modèle de conception de la méthode d'usine que nous avons abordé dans cet article de blog.

Problème

Le modèle de conception Abstract Factory résout les problèmes suivants :

  1. Comment pouvons-nous créer des familles de produits associés tels que : PDFResume , JSONResume et MarkdownResume ?
  2. Comment pouvons-nous prendre en charge plusieurs variantes par famille de produits, telles que : CreativeResume , MinimalistResume et ModernResume ?
  3. Comment pouvons-nous prendre en charge l'ajout de variantes et de produits supplémentaires sans casser notre code de consommation ou client existant ?

Solution

Le modèle de conception Abstract Factory résout ces problèmes en déclarant une interface ou une classe abstraite pour chaque type de produit.

export abstract class PDFResume {}
export abstract class JSONResume {}
export abstract class MarkdownResume {}

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Et puis, comme le nom du modèle l'indique, nous créons une usine abstraite qui est une interface qui déclare des méthodes d'usine qui créent chaque type de produit :

  • createPDFResume : qui renvoie un type ou sous-type PDFResume.
  • createMarkdownResume : qui renvoie un type ou sous-type MarkdownResume.
  • createJSONResume : qui renvoie un type ou sous-type JSONResume.
export interface ResumeFactory {
  createPDFResume(): PDFResume
  createMarkdownResume(): MarkdownResume
  createJSONResume(): JSONResume
}

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

D'accord, nous avons maintenant une usine générique qui renvoie tous les types de produits possibles, mais comment pouvons-nous prendre en charge plusieurs variantes par produit ?

La réponse est en créant une ConcreteFactory qui implémente la usine abstraite ( ResumeFactory ).

export abstract class PDFResume {}
export abstract class JSONResume {}
export abstract class MarkdownResume {}

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Maintenant, pour consommer nos usines dans notre classe client, il suffit de déclarer une variable de type ResumeFactory puis d'instancier la Concrete factory correspondante en fonction de la saisie de l'utilisateur.

Code client :

export interface ResumeFactory {
  createPDFResume(): PDFResume
  createMarkdownResume(): MarkdownResume
  createJSONResume(): JSONResume
}

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Structure

Mastering the Abstract Factory Pattern: A Comprehensive Guide

La structure du modèle de conception Abstract Factory se compose des classes suivantes :

  1. Usine : La raison pour laquelle ce modèle de conception a été nommé usine abstraite est que cette classe représente le contrat entre toutes les ConcreteFactories. Il définit toutes les méthodes d'usine.
  • Le nombre de méthodes d'usine est égal au nombre de produits.
  • Chaque méthode d'usine doit renvoyer un type de produit abstrait ou générique ( IPat{j} ).

Dans notre cas, les méthodes factory déclarées dans Factory sont : createProductA et createProductB

  1. ConcreteFactory{i} : Ces classes implémentent la classe Factory et fournissent des implémentations personnalisées pour chaque méthode d'usine.
  • Dans le schéma ci-dessus, i est égal à 1 ou 2.
  • Le nombre de ConcreteFactories est égal au nombre de variantes possibles par produit.
  • Chaque méthode d'usine concrète doit renvoyer un objet qui est une instance du produit correspondant.
  1. IProduct{j} : Ces classes correspondent aux types de produits abstraits.
  • Dans le schéma ci-dessus, j est égal à A ou B.
  • Chaque IPat{j} est implémenté par de nombreuses classes de produits concrètes.

ConcretProductA1 et ConcretProductA2 implémentent IProductA ConcretProductB1 et ConcretProductB2 implémentent IProductB

  1. Les ConcreteProducts sont les produits qui implémentent l'un des types génériques IProduct{j}.

Scénario pratique

Dans cette section, nous allons mettre l'exemple précédent en action en créant une application CLI Node.js TypeScript entièrement fonctionnelle qui crée un CV basé sur le thème et le format choisis par l'utilisateur.

N'hésitez pas à consulter le code fonctionnel complet en clonant ce référentiel sur votre machine.

Ensuite, exécutez les commandes suivantes :

export abstract class PDFResume {}
export abstract class JSONResume {}
export abstract class MarkdownResume {}

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Déclaration des types

Commençons par déclarer les types que nous utiliserons tout au long du didacticiel pour garantir la sécurité des types.

interfaces/Types

export interface ResumeFactory {
  createPDFResume(): PDFResume
  createMarkdownResume(): MarkdownResume
  createJSONResume(): JSONResume
}

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
  1. Le type ResumeData définit tous les attributs d'un objet de CV tels que : le nom, l'e-mail, le téléphone et un éventail d'expériences.
  2. Le type Expérience comprend : l'entreprise, le poste, la date de début, la date de fin et la description.

Déclarer notre usine abstraite

Maintenant, déclarons le type generic factory, qui définira les trois méthodes factory qui correspondent aux différents types de produits supportés : PDFResume , MarkdownResume , et JSONResume.

interfaces/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
  }
}

Copier après la connexion
Copier après la connexion
Copier après la connexion

Nous passerons en revue leur code dans la section suivante.

Déclarer la classe partagée pour les différents types de documents

Ensuite, passons à la création de nos classes de produits génériques.

Chaque type de produit sera une classe abstraite car nous souhaitons partager à la fois les attributs et les méthodes entre leurs sous-types correspondants.

  1. JSONResume : La classe a un attribut data protégé, stockant un objet de type ResumeData avec un attribut supplémentaire appelé style.

La classe définit :

  • Une méthode getter pour accéder à l'attribut data.
  • Une méthode abstraite generate qui sera remplacée par les sous-classes ultérieurement.
  • Une méthode saveToFile avec une implémentation basique, qui consiste à stocker les données du CV dans un fichier JSON.

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.")
}

Copier après la connexion
Copier après la connexion
Copier après la connexion

Le mot-clé abstract signifie que la classe est un type générique qui ne peut pas être instancié ; il ne peut être hérité que par d'autres classes.

  1. MarkdownResume : La classe a un attribut content protégé, stockant la chaîne de démarque.

La classe définit :

  • Une méthode getter pour accéder à l'attribut content.
  • Une méthode abstraite generate qui sera remplacée par les sous-classes ultérieurement.
  • Une méthode saveToFile qui prend un nom de fichier puis stocke la chaîne formatée markdown content dans un fichier.

reprises/markdown/MarkdownResume

export abstract class PDFResume {}
export abstract class JSONResume {}
export abstract class MarkdownResume {}

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
  1. PDFResume :

La classe a un objet doc protégé de type PDFKit.PDFDocument , qui est importé d'une bibliothèque appelée pdfkit. La bibliothèque simplifie la création et la manipulation de documents PDF grâce à son interface orientée objet.

La classe définit :

  • Une méthode getter pour accéder à l'attribut doc.
  • Une méthode abstraite generate qui sera remplacée par les sous-classes ultérieurement.
  • Une méthode saveToFile qui enregistre l'objet PDF doc en mémoire dans un fichier spécifique.

resumes/pdf/PDFResume

export interface ResumeFactory {
  createPDFResume(): PDFResume
  createMarkdownResume(): MarkdownResume
  createJSONResume(): JSONResume
}

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Déclarer nos usines à béton

Maintenant que nous avons défini nos types de produits génériques et notre usine abstraite , il est temps de procéder à la création de nos ConcreteFactories qui correspondent aux différentes variantes de chaque type de produit générique.

Nous avons 3 variantes possibles pour un CV : Créatif , Minimaliste et Moderne. Et 3 types de produits génériques : JSON , PDF et Markdown.

La fabrique abstraite ( ResumeFactory ) définit les 3 méthodes de fabrique qui sont responsables de la création de nos produits :

  • createPDFResume : crée une instance de type PDFResume.
  • createMarkdownResume : crée une instance de type MarkdownResume.
  • createJSONResume : crée une instance de type JSONResume.

Pour supporter plusieurs variantes par produit, nous devrons créer 3 usines à béton.

Chaque Usine de béton créera les 3 types de produits mais avec ses propres saveurs :

  1. CreativeResumeFactory crée des produits de la variante Creative.
  2. MinimalistResumeFactory crée des produits de la variante minimaliste.
  3. ModernResumeFactory crée des produits de la variante Modern.

usines/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
  }
}

Copier après la connexion
Copier après la connexion
Copier après la connexion
  • Les méthodes d'usine CreativeResumeFactory renvoient la variante de produit concret créatif pour chaque type de produit.

usines/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.")
}

Copier après la connexion
Copier après la connexion
Copier après la connexion
  • Les méthodes d'usine MinimalistResumeFactory renvoient la variante minimaliste du produit en béton pour chaque type de produit.

usines/ModernResumeFactory

export abstract class PDFResume {}
export abstract class JSONResume {}
export abstract class MarkdownResume {}

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
  • Les méthodes d'usine ModernResumeFactory renvoient la variante de produit en béton moderne pour chaque type de produit.

The Creative CV Factory Produits en béton

Maintenant, créons les ConcreteProducts précédents qui sont renvoyés par le CreativeResumeFactory

CV PDF :

resumes/pdf/CreativePDFResume

export interface ResumeFactory {
  createPDFResume(): PDFResume
  createMarkdownResume(): MarkdownResume
  createJSONResume(): JSONResume
}

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Reprise de démarque :

resumes/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
  }
}

Copier après la connexion
Copier après la connexion
Copier après la connexion

CV JSON :

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.")
}

Copier après la connexion
Copier après la connexion
Copier après la connexion

L'usine de CV minimaliste Produits en béton

Ensuite, créons les ConcreteProducts précédents qui sont renvoyés par le MinimalistResumeFactory

CV PDF :

resumes/pdf/MinimalistPDFResume

npm install
npm start

Copier après la connexion

Reprise de démarque :

resumes/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
}

Copier après la connexion

CV 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
}

Copier après la connexion

L'usine de CV moderne Produits en béton

Enfin, créons les ConcreteProducts précédents qui sont renvoyés par le ModernResumeFactory

CV PDF :

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
  }
}

Copier après la connexion

Reprise de démarque :

resumes/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
  }
}

Copier après la connexion

CV JSON :

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
  }
}

Copier après la connexion

Utilisation de nos usines dans notre fichier Index.ts

Commençons à porter les fruits de notre travail précédent en utilisant nos usines dans le code client.

Regardez comment nous pouvons désormais utiliser notre bibliothèque de création de CV de manière très propre en utilisant simplement nos usines.

L'utilisateur ne doit fournir que deux choses :

  1. Le type de produit : Quel type de PDF souhaite-t-il créer ?
  2. Le thème : Quel genre de styles de CV préfère-t-il ?

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
  }
}

Copier après la connexion

Le code ci-dessus fonctionne en trois étapes :

  1. Entrée utilisateur : Nous obtenons d'abord les valeurs thème et format.
  2. Choisir une usine : Ensuite, nous instancions l'usine correspondante en fonction de la valeur du thème.
  3. Création du Produit : Enfin, on appelle la méthode factory correspondante en fonction du format choisi.

L'utilisateur ne se soucie pas de la façon dont les produits et leurs variantes correspondantes sont créés ; il leur suffit de sélectionner un thème et un format , et c'est tout - le produit correspondant est créé comme demandé.

Le code client est désormais robuste aux modifications. Si nous voulons ajouter un nouveau thème ou style, nous pouvons simplement créer une nouvelle usine qui se chargera de le faire.

Nous avons utilisé la bibliothèque de craie pour colorer nos journaux de terminal en fonction de leur signification sémantique.

Pour pouvoir obtenir les entrées de l'utilisateur de l'application CLI, nous avons utilisé le package inquirer, qui fournit un moyen vraiment attrayant et convivial d'obtenir différents types d'entrées de l'utilisateur.

  1. La fonction getUserInput a été utilisée pour obtenir les principales informations du CV : nom, email, téléphone.
  2. La fonction utilitaire getExperience a été utilisée pour récupérer de manière récursive les informations d'expérience de l'utilisateur. En d’autres termes, il invite l’utilisateur à renseigner les informations d’expérience pour la première entrée, puis lui demande s’il a une autre expérience à ajouter. Si la réponse est non, la fonction renvoie simplement ; en revanche, s'ils sélectionnent oui, il leur sera à nouveau demandé de renseigner les informations de la prochaine expérience.

utils/userInput

export abstract class PDFResume {}
export abstract class JSONResume {}
export abstract class MarkdownResume {}

Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion
Copier après la connexion

Conclusion

Le modèle Abstract Factory est un outil puissant dans l'arsenal des concepteurs et des développeurs de logiciels. Il fournit une approche structurée pour créer des familles d'objets associés sans spécifier leurs classes concrètes. Ce modèle est particulièrement utile lorsque :

  1. Un système doit être indépendant de la façon dont ses produits sont créés, composés et représentés.
  2. Un système doit être configuré avec l'une des multiples familles de produits.
  3. Une famille d'objets produits associés est conçue pour être utilisée ensemble, et vous devez appliquer cette contrainte.
  4. Vous souhaitez fournir une bibliothèque de classes de produits et vous souhaitez révéler uniquement leurs interfaces, pas leurs implémentations.

Dans notre exemple pratique, nous avons vu comment le modèle Abstract Factory peut être appliqué pour créer un système de génération de CV flexible et extensible. Ce système peut facilement s'adapter à de nouveaux styles de CV ou formats de sortie sans modifier le code existant, démontrant ainsi la puissance du principe ouvert/fermé en action.

Bien que le modèle Abstract Factory offre de nombreux avantages, il est important de noter qu'il peut introduire une complexité supplémentaire dans votre base de code. Par conséquent, il est crucial d’évaluer si la flexibilité qu’elle offre est nécessaire pour votre cas d’utilisation spécifique.

En maîtrisant les modèles de conception comme Abstract Factory, vous serez mieux équipé pour créer des systèmes logiciels robustes, flexibles et maintenables. Continuez à explorer et à appliquer ces modèles dans vos projets pour améliorer vos compétences en conception de logiciels.

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!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal