Alors l'ère de l'IA est là, l'énorme bond en avant qui, en ce moment, crache du code Node avec const fetch = require('node-fetch') ? (vrai pour ChatGPT et Gemini à partir d'aujourd'hui) et alimente encore une autre tournure de la machine cyclique qu'est Internet et son contenu.
Dans cet amalgame de contenu, des modèles de conception réapparaissent
Des articles expliquant comment appliquer des modèles de conception dans Node (???) aux articles expliquant avec tous les détails des éléments obsolètes comme comment appliquer le modèle d'usine en Java (Java 8 publié en mars 2014 a ajouté Lambdas ).
Vous êtes déjà tombé sur un gourou du refactoring ?
Il s'agit d'un site Web que vous avez probablement visité au cours de votre parcours d'apprentissage en informatique, notamment en programmation. Sa section sur les modèles de conception est assez bien expliquée et l'une des plus partagées sur différents forums au fil des ans.
Si l'on va à la définition de ce que sont les modèles de conception, on trouve :
Les modèles de conception sont des solutions typiques à des problèmes courants
dans la conception de logiciels. Chaque motif est comme un plan
que vous pouvez personnaliser pour résoudre un problème particulier
problème de conception dans votre code.
Pourquoi ce post, alors ? Je veux dire, il y a beaucoup d'informations sur le site Web lié ci-dessus ; ça pourrait être tout.
Le fait est que j'ai toujours eu du mal à accepter cette définition... "pour résoudre un problème de conception particulier dans mon code"... dans mon code ? Mon code présente-t-il un problème que je dois résoudre ?
Ce qui se passe réellement, c'est que j'ai besoin de coder un certain "quelque chose" pour lequel le langage de programmation utilisé dans le projet manque d'abstractions pour.
Clairement et simplement. Juste au cas où cela ne vous concerne pas encore, voyons quelques exemples avec du code.
Il s'agit d'une implémentation très simple du Factory Pattern en Java (principalement un langage de programmation orienté objet).
public class ShapeFactory { public Shape createShape(String type) { if (type.equalsIgnoreCase("CIRCLE")) { return new Circle(); } else if (type.equalsIgnoreCase("SQUARE")) { return new Square(); } return null; } }
Puis Java 8 (mars 2014, juste au cas où vous auriez oublié) a ajouté Lambdas (un concept issu de la programmation fonctionnelle) afin que nous puissions faire ceci à la place :
Map<String, Supplier<Shape>> shapeFactory = new HashMap<>(); shapeFactory.put("CIRCLE", Circle::new); shapeFactory.put("SQUARE", Square::new); Shape circle = shapeFactory.get("CIRCLE").get();
Plus jamais besoin du modèle de conception d'usine (du moins en Java).
Oui, je sais que le modèle d'usine est l'exemple que la plupart des gens utilisent tout le temps, mais que se passe-t-il avec les autres ? Et que se passe-t-il dans les autres langages de programmation ?
Voici le modèle de visiteur dans Typescript :
interface Shape { draw(): void; accept(visitor: ShapeVisitor): void; } class Circle implements Shape { radius: number; constructor(radius: number) { this.radius = radius; } draw() { console.log("Drawing a circle"); } accept(visitor: ShapeVisitor) { visitor.visitCircle(this); } } class Square implements Shape { sideLength: number; constructor(sideLength: number) { this.sideLength = sideLength; } draw() { console.log("Drawing a square"); } accept(visitor: ShapeVisitor) { visitor.visitSquare(this); } } interface ShapeVisitor { visitCircle(circle: Circle): void; visitSquare(square: Square): void; } class AreaCalculator implements ShapeVisitor { private area = 0; visitCircle(circle: Circle) { this.area = Math.PI * circle.radius * circle.radius; console.log(`Circle area: ${this.area}`); } visitSquare(square: Square) { this.area = square.sideLength * square.sideLength; console.log(`Square area: ${this.area}`); } getArea(): number { return this.area; } } // Using the Visitor const circle = new Circle(5); const square = new Square(4); const calculator = new AreaCalculator(); circle.accept(calculator); square.accept(calculator);
Le code suivant fait exactement la même chose mais en utilisant la réflexion (la capacité d'un langage à examiner et manipuler ses propres objets au moment de l'exécution) au lieu du modèle Visiteur :
interface Shape { draw(): void; } class Circle implements Shape { // ... (same as before) radius: number; } class Square implements Shape { // ... (same as before) sideLength: number; } function calculateArea(shape: Shape) { if (shape instanceof Circle) { const circle = shape as Circle; // Type assertion const area = Math.PI * circle.radius * circle.radius; console.log(`Circle area: ${area}`); } else if (shape instanceof Square) { const square = shape as Square; // Type assertion const area = square.sideLength * square.sideLength; console.log(`Square area: ${area}`); } } const circle = new Circle(5); const square = new Square(4); calculateArea(circle); calculateArea(square);
Maintenant le modèle d'observateur, également en TypeScript :
interface Observer { update(data: any): void; } class NewsPublisher { private observers: Observer[] = []; subscribe(observer: Observer) { this.observers.push(observer); } unsubscribe(observer: Observer) { this.observers = this.observers.filter(o => o !== observer); } notify(news: string) { this.observers.forEach(observer => observer.update(news)); } } class NewsletterSubscriber implements Observer { update(news: string) { console.log(`Received news: ${news}`); } } // Using the Observer const publisher = new NewsPublisher(); const subscriber1 = new NewsletterSubscriber(); const subscriber2 = new NewsletterSubscriber(); publisher.subscribe(subscriber1); publisher.subscribe(subscriber2); publisher.notify("New product launched!");
La même chose mais en utilisant l'EventEmitter intégré (dans l'API Node) :
public class ShapeFactory { public Shape createShape(String type) { if (type.equalsIgnoreCase("CIRCLE")) { return new Circle(); } else if (type.equalsIgnoreCase("SQUARE")) { return new Square(); } return null; } }
À ce stade, vous avez peut-être réalisé que le « problème » est la mise en œuvre de la POO, et vous auriez tout à fait raison, mais pas entièrement.
Chaque paradigme de programmation, surtout lorsqu'il est pris dans sa forme la plus pure, a ses bizarreries, ses difficultés ou « des choses qui ne peuvent pas être réalisées en ligne droite », si vous préférez.
Passons au domaine de la programmation fonctionnelle. Vous avez probablement entendu parler des Monades.
Que vous soyez tombé ou non dans le piège mental de la définition mathématique, nous - les développeurs de logiciels - pouvons également comprendre les monades comme des modèles de conception. En effet, dans un monde de fonctions pures, où rien d'inattendu ne se produit, il est difficile de concevoir un effet secondaire, mais la plupart des produits logiciels ont besoin d'effets secondaires, alors comment pouvons-nous... ?
Voici un exemple de la monade IO en Haskell :
Map<String, Supplier<Shape>> shapeFactory = new HashMap<>(); shapeFactory.put("CIRCLE", Circle::new); shapeFactory.put("SQUARE", Square::new); Shape circle = shapeFactory.get("CIRCLE").get();
L'effet secondaire (lecture d'un fichier) est contenu dans la monade IO.
Ajoutons un exemple monadique en utilisant TypeScript ;
interface Shape { draw(): void; accept(visitor: ShapeVisitor): void; } class Circle implements Shape { radius: number; constructor(radius: number) { this.radius = radius; } draw() { console.log("Drawing a circle"); } accept(visitor: ShapeVisitor) { visitor.visitCircle(this); } } class Square implements Shape { sideLength: number; constructor(sideLength: number) { this.sideLength = sideLength; } draw() { console.log("Drawing a square"); } accept(visitor: ShapeVisitor) { visitor.visitSquare(this); } } interface ShapeVisitor { visitCircle(circle: Circle): void; visitSquare(square: Square): void; } class AreaCalculator implements ShapeVisitor { private area = 0; visitCircle(circle: Circle) { this.area = Math.PI * circle.radius * circle.radius; console.log(`Circle area: ${this.area}`); } visitSquare(square: Square) { this.area = square.sideLength * square.sideLength; console.log(`Square area: ${this.area}`); } getArea(): number { return this.area; } } // Using the Visitor const circle = new Circle(5); const square = new Square(4); const calculator = new AreaCalculator(); circle.accept(calculator); square.accept(calculator);
Un classique, j'ai vu la monade peut-être environ 50 fois partout sur Internet, mais qu'est-ce que c'est, vraiment ?
Le problème qu'il essaie de résoudre :
interface Shape { draw(): void; } class Circle implements Shape { // ... (same as before) radius: number; } class Square implements Shape { // ... (same as before) sideLength: number; } function calculateArea(shape: Shape) { if (shape instanceof Circle) { const circle = shape as Circle; // Type assertion const area = Math.PI * circle.radius * circle.radius; console.log(`Circle area: ${area}`); } else if (shape instanceof Square) { const square = shape as Square; // Type assertion const area = square.sideLength * square.sideLength; console.log(`Square area: ${area}`); } } const circle = new Circle(5); const square = new Square(4); calculateArea(circle); calculateArea(square);
On a oublié de définir les propriétés de notre objet ! ?
dans un cas d'utilisation réel, il s'agirait principalement de l'entrée d'un effet secondaire, comme la lecture d'une base de données ou d'un fichier
Alors maintenant, si nous le faisons :
interface Observer { update(data: any): void; } class NewsPublisher { private observers: Observer[] = []; subscribe(observer: Observer) { this.observers.push(observer); } unsubscribe(observer: Observer) { this.observers = this.observers.filter(o => o !== observer); } notify(news: string) { this.observers.forEach(observer => observer.update(news)); } } class NewsletterSubscriber implements Observer { update(news: string) { console.log(`Received news: ${news}`); } } // Using the Observer const publisher = new NewsPublisher(); const subscriber1 = new NewsletterSubscriber(); const subscriber2 = new NewsletterSubscriber(); publisher.subscribe(subscriber1); publisher.subscribe(subscriber2); publisher.notify("New product launched!");
le programme explose.
La solution sans la monade Maybe :
import { EventEmitter } from 'events'; class NewsPublisher extends EventEmitter { publish(news: string) { this.emit('news', news); } } const publisher = new NewsPublisher(); publisher.on('news', (news) => { console.log(`All subscribers received the news: ${news}`); }); publisher.publish("New product launched!");
Le programme n'explose pas.
La peut-être la monade n'est pas nécessaire en JavaScript ou en dactylographie en raison de l'opérateur de chaînage facultatif, mais si vous utilisez un langage qui ne l'implémente pas... eh bien, vous pouvez appliquer la monade peut-être ou devrais-je dire modèle de conception ?
Oui, je sais, il y a des gens qui viennent d'apprendre le truc Maybe et l'ont appliqué avec impatience à 6 projets parallèles en même temps et maintenant je suis en train de rire à la fête en vous disant "vous n'en avez pas besoin". Mais tu peux toujours l'utiliser, d'ailleurs je t'invite à le faire si tu trouves que c'est cool (en fin de compte c'est ton code avec cette jolie bouille tu peux faire ce que tu veux ! ?)
Mais revenons à l’essentiel. Qu’en est-il des autres paradigmes ? Si vous sortez des sentiers battus POO/FP, j'aime ça !
Tous les paradigmes ont définitivement leurs propres solutions et techniques récurrentes, même s'ils ne sont pas toujours formellement appelés « modèles de conception ».
Voici quelques exemples (merci Gémeaux de m'avoir évité de réfléchir, merci pour le joli formatage et la valeur ajoutée ?) :
Il existe beaucoup de "techniques" et de "modèles", cette liste est juste pour vous donner des fils de discussion si vous êtes curieux.
J'espère que cela vous sera utile, je vous lis assez vite !
Bien que le terme « modèles de conception » soit le plus étroitement associé à la POO, d'autres paradigmes ont leurs propres ensembles de solutions et de techniques récurrentes. Ces techniques abordent les défis et contraintes spécifiques de ces paradigmes, fournissant des approches établies aux problèmes communs. Ainsi, même s'ils ne sont pas toujours formellement qualifiés de « modèles de conception », ils remplissent un objectif similaire en guidant les développeurs vers des solutions efficaces et maintenables.
Nous pouvons comprendre les modèles de conception comme des solutions de contournement bien connues pour corriger des fonctionnalités pour lesquelles le langage de programmation que nous utilisons manque d'abstractions.
Cet article a été écrit presque entièrement par moi, exemples spécifiés par Gemini 1.5 Pro
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!