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.
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.
Le modèle de conception Abstract Factory résout les problèmes suivants :
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 {}
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 :
export interface ResumeFactory { createPDFResume(): PDFResume createMarkdownResume(): MarkdownResume createJSONResume(): JSONResume }
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 {}
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 }
La structure du modèle de conception Abstract Factory se compose des classes suivantes :
Dans notre cas, les méthodes factory déclarées dans Factory sont : createProductA et createProductB
ConcretProductA1 et ConcretProductA2 implémentent IProductA ConcretProductB1 et ConcretProductB2 implémentent IProductB
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 {}
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 }
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 } }
Nous passerons en revue leur code dans la section suivante.
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.
La classe définit :
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.") }
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.
La classe définit :
reprises/markdown/MarkdownResume
export abstract class PDFResume {} export abstract class JSONResume {} export abstract class MarkdownResume {}
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 :
resumes/pdf/PDFResume
export interface ResumeFactory { createPDFResume(): PDFResume createMarkdownResume(): MarkdownResume createJSONResume(): JSONResume }
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 :
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 :
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 } }
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.") }
usines/ModernResumeFactory
export abstract class PDFResume {} export abstract class JSONResume {} export abstract class MarkdownResume {}
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 }
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 } }
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.") }
Ensuite, créons les ConcreteProducts précédents qui sont renvoyés par le MinimalistResumeFactory
CV PDF :
resumes/pdf/MinimalistPDFResume
npm install npm start
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 }
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 }
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 } }
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 } }
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 } }
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 :
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 } }
Le code ci-dessus fonctionne en trois étapes :
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.
utils/userInput
export abstract class PDFResume {} export abstract class JSONResume {} export abstract class MarkdownResume {}
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 :
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.
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!