Maison > interface Web > js tutoriel > Démystifier les modèles de conception

Démystifier les modèles de conception

Susan Sarandon
Libérer: 2024-11-03 10:54:02
original
352 Les gens l'ont consulté

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

Demystifying Design Patterns

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

Définition

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 ?

Définition, réinventée

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;   
  }
}
Copier après la connexion
Copier après la connexion

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();
Copier après la connexion
Copier après la connexion

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); 
Copier après la connexion
Copier après la connexion

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);
Copier après la connexion
Copier après la connexion

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!");
Copier après la connexion
Copier après la connexion

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;   
  }
}
Copier après la connexion
Copier après la connexion

À 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();
Copier après la connexion
Copier après la connexion

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); 
Copier après la connexion
Copier après la connexion

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);
Copier après la connexion
Copier après la connexion

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!");
Copier après la connexion
Copier après la connexion

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!");
Copier après la connexion

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 ?) :

Programmation logique :
  • Programmation logique par contraintes : ce paradigme implique de définir des contraintes et des relations entre les variables, puis de laisser le système trouver des solutions qui satisfont ces contraintes. Des techniques telles que le retour en arrière et la propagation de contraintes sont cruciales pour une résolution efficace des problèmes dans ce paradigme. (Très utile lorsqu'il s'agit d'IA).
  • Bases de données déductives : ces bases de données utilisent des règles logiques et des inférences pour dériver de nouvelles informations à partir de données existantes. Des techniques telles que le chaînage avant/arrière sont fondamentales dans le fonctionnement de ces bases de données et pourraient être considérées comme des modèles au sein de ce paradigme.
Programmation simultanée :
  • Transmission de messages : dans les systèmes concurrents, où plusieurs processus s'exécutent simultanément, la transmission de messages est une technique courante de communication et de coordination. Des modèles tels que producteur-consommateur et lecteur-écrivain fournissent des solutions établies pour gérer l'accès simultané aux ressources et garantir la cohérence des données.
  • Primitives de synchronisation : ce sont des constructions de bas niveau telles que des mutex, des sémaphores et des variables de condition qui sont utilisées pour contrôler l'accès aux ressources partagées. dans les programmes concurrents. Bien qu'il ne s'agisse pas de « modèles » au sens traditionnel du terme, ils représentent des solutions bien définies aux défis courants de concurrence.

Programmation orientée données :

  • Pipelines de transformation de données : ce paradigme met l'accent sur la transformation des données à travers une série d'opérations. Des techniques telles que map, filter et reduce (également courantes dans la programmation fonctionnelle et BEAUCOUP utilisées en javascript depuis son ajout) sont des éléments fondamentaux pour la construction ces pipelines, et pourraient être considérés comme des modèles au sein de ce paradigme.
  • Entity-Component-System (ECS) : ce modèle architectural est populaire dans le développement de jeux et d'autres applications gourmandes en données. Cela implique de décomposer les entités en composants (données) et systèmes (logique), en favorisant la localité des données et un traitement efficace.

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 !

Demystifying Design Patterns


? Résumé, pour les pressés !

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!

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