Modèles de conception JavaScript

L'auteur du matériel, dont nous publions la traduction, dit qu'au démarrage d'un projet, il ne commence pas immédiatement à écrire du code. Tout d'abord, ils déterminent le but et les limites du projet, puis - identifient les opportunités qu'il devrait avoir. Après cela, soit ils écrivent immédiatement le code, soit, s'il s'agit d'un projet assez complexe, ils sélectionnent des modèles de conception appropriés qui en constituent la base. Ce matériel concerne les modèles de conception JavaScript. Il est conçu principalement pour les développeurs débutants.



Qu'est-ce qu'un modèle de conception?


Dans le domaine du développement logiciel, un modèle de conception est une conception architecturale reproductible qui est une solution à un problème de conception dans un contexte qui se pose souvent. Les modèles de conception sont un résumé de l'expérience des développeurs de logiciels professionnels. Un modèle de conception peut être considéré comme une sorte de modèle selon lequel les programmes sont écrits.

Pourquoi des modèles de conception sont-ils nécessaires?


De nombreux programmeurs pensent que les modèles de conception sont une perte de temps ou ne savent tout simplement pas comment les appliquer correctement. Cependant, l'utilisation d'un modèle approprié peut aider à écrire du code meilleur et plus compréhensible, qui, en raison de sa compréhensibilité, sera plus facile à maintenir.

La chose la plus importante ici, peut-être, est que l'utilisation de modèles donne aux développeurs de logiciels quelque chose comme un dictionnaire de termes bien connus qui sont très utiles, par exemple, pour analyser le code de quelqu'un d'autre. Les modèles révèlent le but de certains fragments du programme pour ceux qui essaient de gérer l'appareil d'un projet.

Par exemple, si vous utilisez le modèle «Décorateur», il informera immédiatement le nouveau programmeur qui est venu au projet des tâches qu'un élément de code particulier résout et pourquoi il est nécessaire. Grâce à cela, un tel programmeur pourra consacrer plus de temps aux tâches pratiques que le programme résout, plutôt que d'essayer de comprendre sa structure interne.

Maintenant que nous avons compris ce que sont les modèles de conception et à quoi ils servent, nous allons passer aux modèles eux-mêmes et décrire leur implémentation à l'aide de JavaScript.

Modèle "Module"


Un module est un morceau de code indépendant qui peut être modifié sans affecter un autre code de projet. De plus, les modules permettent d'éviter un phénomène tel que la pollution des zones de visibilité du fait qu'ils créent des zones de visibilité distinctes pour les variables qui y sont déclarées. Les modules écrits pour un projet peuvent être réutilisés dans d'autres projets dans le cas où leurs mécanismes sont universels et non liés aux spécificités d'un projet particulier.

Les modules font partie intégrante de toute application JavaScript moderne. Ils aident à maintenir la propreté du code, à séparer le code en fragments significatifs et à l'organiser. JavaScript a plusieurs façons de créer des modules, dont le modèle «Module».

Contrairement à d'autres langages de programmation, JavaScript n'a pas de modificateurs d'accès. Autrement dit, les variables ne peuvent pas être déclarées privées ou publiques. Par conséquent, le modèle «Module» est également utilisé pour émuler le concept d'encapsulation.

Ce modèle utilise l'IFEF (expression fonctionnelle invoquée immédiatement), les fermetures et les étendues de fonction pour imiter ce concept. Par exemple:

const myModule = (function() {   const privateVariable = 'Hello World';   function privateMethod() {    console.log(privateVariable);  }  return {    publicMethod: function() {      privateMethod();    }  } })(); myModule.publicMethod(); 

Puisque nous avons IIFE, le code est exécuté immédiatement et l'objet retourné par l'expression est affecté à la constante myModule . En raison du fait qu'il y a une fermeture, l'objet retourné a accès aux fonctions et variables déclarées à l'intérieur de IIFE, même une fois que IIFE a terminé son travail.

Par conséquent, les variables et fonctions déclarées à l'intérieur de l'IIFF sont cachées aux mécanismes qui sont dans le champ de visibilité extérieur à eux. Ils s'avèrent être des entités privées de la constante myModule .

Une fois ce code exécuté, myModule ressemblera à ceci:

 const myModule = { publicMethod: function() {   privateMethod(); }}; 

Autrement dit, en faisant référence à cette constante, vous pouvez appeler la méthode publique de l'objet publicMethod() , qui, à son tour, appellera la méthode privée privateMethod() . Par exemple:

 //  'Hello World' module.publicMethod(); 

Modèle de module ouvert


Le modèle de module révélateur est une version légèrement améliorée du modèle de module proposé par Christian Heilmann. Le problème avec le modèle «Module» est que nous devons créer des fonctions publiques juste pour accéder aux fonctions et variables privées.

Dans ce modèle, nous attribuons des fonctions privées aux propriétés de l'objet retourné que nous voulons rendre public. C'est pourquoi ce modèle est appelé le «module ouvert». Prenons un exemple:

 const myRevealingModule = (function() { let privateVar = 'Peter'; const publicVar  = 'Hello World'; function privateFunction() {   console.log('Name: '+ privateVar); } function publicSetName(name) {   privateVar = name; } function publicGetName() {   privateFunction(); } /**    ,     */ return {   setName: publicSetName,   greeting: publicVar,   getName: publicGetName }; })(); myRevealingModule.setName('Mark'); //  Name: Mark myRevealingModule.getName(); 

L'application de ce modèle facilite la compréhension des fonctions et des variables du module qui sont accessibles au public, ce qui contribue à améliorer la lisibilité du code.

Après avoir exécuté IIFE, myRevealingModule ressemble à ceci:

 const myRevealingModule = { setName: publicSetName, greeting: publicVar, getName: publicGetName }; 

Nous pouvons, par exemple, appeler la myRevealingModule.setName('Mark') , qui est une référence à la fonction interne publicSetName . La méthode myRevealingModule.getName() fait référence à la fonction interne publicGetName . Par exemple:

 myRevealingModule.setName('Mark'); //  Name: Mark myRevealingModule.getName(); 

Considérez les avantages du modèle "Open Module" par rapport au modèle "Module":

  • Le "module ouvert" vous permet de rendre publiques les entitĂ©s cachĂ©es du module (et de les masquer Ă  nouveau si nĂ©cessaire), en modifiant, pour chacune d'elles, une seule ligne dans l'objet retournĂ© après IIFE.
  • L'objet renvoyĂ© ne contient pas de dĂ©finition de fonction. Tout ce qui se trouve Ă  droite de ses noms de propriĂ©tĂ© est dĂ©fini dans IIFE. Cela permet de garder le code propre et facile Ă  lire.

Modules dans ES6


Avant la publication de la norme ES6, JavaScript ne disposait pas d'un outil standard pour travailler avec les modules, par conséquent, les développeurs devaient utiliser des bibliothèques tierces ou le modèle «Module» pour implémenter les mécanismes appropriés. Mais avec l'avènement d'ES6, un système de modules standard est apparu en JavaScript.

Les modules ES6 sont stockés dans des fichiers. Un fichier ne peut contenir qu'un seul module. Tout à l'intérieur du module est privé par défaut. Les fonctions, variables et classes peuvent être rendues publiques à l'aide du mot-clé d' export . Le code à l'intérieur du module est toujours exécuté en mode strict.

â–Ť Module d'exportation


Il existe deux façons d'exporter une fonction ou une variable déclarée dans un module:

  • L'exportation se fait en ajoutant le mot-clĂ© export avant de dĂ©clarer une fonction ou une variable. Par exemple:

     // utils.js export const greeting = 'Hello World'; export function sum(num1, num2) { console.log('Sum:', num1, num2); return num1 + num2; } export function subtract(num1, num2) { console.log('Subtract:', num1, num2); return num1 - num2; } //  -   function privateLog() { console.log('Private Function'); } 
  • L'exportation se fait en ajoutant le mot-clĂ© d' export Ă  la fin du code rĂ©pertoriant les noms des fonctions et des variables Ă  exporter. Par exemple:

     // utils.js function multiply(num1, num2) { console.log('Multiply:', num1, num2); return num1 * num2; } function divide(num1, num2) { console.log('Divide:', num1, num2); return num1 / num2; } //    function privateLog() { console.log('Private Function'); } export {multiply, divide}; 

â–Ť Module d'importation


Tout comme il existe deux façons d'exporter, il existe deux façons d'importer des modules. Cela se fait à l'aide du mot clé import :

  • Importez plusieurs Ă©lĂ©ments sĂ©lectionnĂ©s. Par exemple:

     // main.js //     import { sum, multiply } from './utils.js'; console.log(sum(3, 7)); console.log(multiply(3, 7)); 
  • Importez tout ce que le module exporte. Par exemple:

     // main.js //  ,    import * as utils from './utils.js'; console.log(utils.sum(3, 7)); console.log(utils.multiply(3, 7)); 

▍ Alias ​​pour les entités exportées et importées


Si les noms des fonctions ou des variables exportées dans le code peuvent provoquer une collision, ils peuvent être modifiés lors de l'exportation ou de l'importation.

Pour renommer des entités lors de l'exportation, vous pouvez procéder comme suit:

 // utils.js function sum(num1, num2) { console.log('Sum:', num1, num2); return num1 + num2; } function multiply(num1, num2) { console.log('Multiply:', num1, num2); return num1 * num2; } export {sum as add, multiply}; 

Pour renommer des entités lors de l'importation, la construction suivante est utilisée:

 // main.js import { add, multiply as mult } from './utils.js'; console.log(add(3, 7)); console.log(mult(3, 7)); 

Motif singleton


Le motif «Singleton» ou «Singleton» est un objet qui ne peut exister qu'en une seule copie. Dans le cadre de l'application de ce modèle, une nouvelle instance d'une classe est créée si elle n'a pas encore été créée. Si l'instance de classe existe déjà, lors de la tentative d'accès au constructeur, une référence à l'objet correspondant est renvoyée. Les appels suivants au constructeur renverront toujours le même objet.

En fait, ce que nous appelons le modèle «Singleton» a toujours existé en JavaScript, mais ils ne l'appellent pas «Singleton», mais «objet littéral». Prenons un exemple:

 const user = { name: 'Peter', age: 25, job: 'Teacher', greet: function() {   console.log('Hello!'); } }; 

Étant donné que chaque objet en JavaScript occupe sa propre zone de mémoire et ne la partage pas avec d'autres objets, chaque fois que nous accédons à la variable user , nous obtenons un lien vers le même objet.

Le modèle Singleton peut être implémenté à l'aide de la fonction constructeur. Cela ressemble à ceci:

 let instance = null; function User(name, age) { if(instance) {   return instance; } instance = this; this.name = name; this.age = age; return instance; } const user1 = new User('Peter', 25); const user2 = new User('Mark', 24); //  true console.log(user1 === user2); 

Lorsque la fonction constructeur est appelée, elle vérifie d'abord si l'objet instance existe. Si la variable correspondante n'est pas initialisée, this écrite dans l' instance . Si la variable a déjà une référence à un objet, le constructeur renvoie simplement une instance , c'est-à-dire une référence à un objet existant.

Le modèle Singleton peut être implémenté à l'aide du modèle Module. Par exemple:

 const singleton = (function() { let instance; function User(name, age) {   this.name = name;   this.age = age; } return {   getInstance: function(name, age) {     if(!instance) {       instance = new User(name, age);     }     return instance;   } } })(); const user1 = singleton.getInstance('Peter', 24); const user2 = singleton.getInstance('Mark', 26); // prints true console.log(user1 === user2); 

Ici, nous créons une nouvelle instance d' user en appelant la méthode singleton.getInstance() . Si une instance de l'objet existe déjà, cette méthode la retournera simplement. S'il n'existe pas encore un tel objet, la méthode en crée une nouvelle instance en appelant la fonction constructeur User .

Modèle d'usine


Le modèle Factory utilise ce qu'on appelle des méthodes d'usine pour créer des objets. Vous n'avez pas besoin de spécifier des classes ou des fonctions constructeur utilisées pour créer des objets.

Ce modèle est utilisé pour créer des objets dans les cas où il n'est pas nécessaire de rendre publique la logique de leur création. Le modèle Factory peut être utilisé si vous devez créer différents objets en fonction de conditions spécifiques. Par exemple:

 class Car{ constructor(options) {   this.doors = options.doors || 4;   this.state = options.state || 'brand new';   this.color = options.color || 'white'; } } class Truck { constructor(options) {   this.doors = options.doors || 4;   this.state = options.state || 'used';   this.color = options.color || 'black'; } } class VehicleFactory { createVehicle(options) {   if(options.vehicleType === 'car') {     return new Car(options);   } else if(options.vehicleType === 'truck') {     return new Truck(options);     } } } 

Les classes Car et Truck sont créées ici, qui prévoient l'utilisation de certaines valeurs standard. Ils sont utilisés pour créer truck objets de car et de truck . La classe VehicleFactory est également déclarée ici, qui est utilisée pour créer de nouveaux objets en fonction de l'analyse de la propriété vehicleType , passée à la méthode correspondante de l'objet qu'elle renvoie dans l'objet avec des options . Voici comment travailler avec tout cela:

 const factory = new VehicleFactory(); const car = factory.createVehicle({ vehicleType: 'car', doors: 4, color: 'silver', state: 'Brand New' }); const truck= factory.createVehicle({ vehicleType: 'truck', doors: 2, color: 'white', state: 'used' }); //  Car {doors: 4, state: "Brand New", color: "silver"} console.log(car); //  Truck {doors: 2, state: "used", color: "white"} console.log(truck); 

L'objet factory de la classe VehicleFactory est VehicleFactory . Après cela, vous pouvez créer des objets des classes Car ou Truck en appelant la méthode factory.createVehicle() et en lui passant l'objet options avec la propriété vehicleType définie sur car ou truck .

Motif de décorateur


Le modèle Decorator est utilisé pour étendre les fonctionnalités des objets sans modifier les classes ou les fonctions constructeurs existantes. Ce modèle peut être utilisé pour ajouter certaines fonctionnalités aux objets sans modifier le code responsable de leur création.

Voici un exemple simple d'utilisation de ce modèle:

 function Car(name) { this.name = name; //    this.color = 'White'; } //   ,    const tesla= new Car('Tesla Model 3'); //   -    tesla.setColor = function(color) { this.color = color; } tesla.setPrice = function(price) { this.price = price; } tesla.setColor('black'); tesla.setPrice(49000); //  black console.log(tesla.color); 

Considérons maintenant un exemple pratique de l'application de ce modèle. Supposons que le coût des voitures dépend de leurs caractéristiques, des fonctions supplémentaires à leur disposition. Sans l'utilisation du modèle Decorator, pour décrire ces voitures, nous aurions à créer différentes classes pour différentes combinaisons de ces fonctions supplémentaires, chacune ayant une méthode pour trouver le coût d'une voiture. Par exemple, cela pourrait ressembler à ceci:

 class Car() { } class CarWithAC() { } class CarWithAutoTransmission { } class CarWithPowerLocks { } class CarWithACandPowerLocks { } 

Grâce au modèle en question, vous pouvez créer une Car classe de base, décrivant, disons, une voiture dans la configuration de base, dont le coût est exprimé par un montant fixe. Après cela, l'objet standard créé sur la base de cette classe peut être développé à l'aide des fonctions de décoration. La «voiture» standard traitée par cette fonction obtient de nouvelles opportunités, ce qui affecte en outre son prix. Par exemple, ce schéma peut être implémenté comme suit:

 class Car { constructor() { //   this.cost = function() { return 20000; } } } // - function carWithAC(car) { car.hasAC = true; const prevCost = car.cost(); car.cost = function() {   return prevCost + 500; } } // - function carWithAutoTransmission(car) { car.hasAutoTransmission = true;  const prevCost = car.cost(); car.cost = function() {   return prevCost + 2000; } } // - function carWithPowerLocks(car) { car.hasPowerLocks = true; const prevCost = car.cost(); car.cost = function() {   return prevCost + 500; } } 

Ici, nous créons d'abord la classe de base Car , utilisée pour créer des objets qui représentent les voitures en standard. Ensuite, nous créons plusieurs fonctions décoratives qui nous permettent d'étendre les objets de la classe Car base avec des propriétés supplémentaires. Ces fonctions prennent les objets correspondants comme paramètres. Après cela, nous ajoutons une nouvelle propriété à l'objet, indiquant de quelle nouvelle fonctionnalité la voiture sera équipée, et redéfinissons la fonction de cost de l'objet, qui renvoie désormais le nouveau coût de la voiture. Par conséquent, afin «d'équiper» la voiture de configuration standard avec quelque chose de nouveau, nous pouvons utiliser la conception suivante:

 const car = new Car(); console.log(car.cost()); carWithAC(car); carWithAutoTransmission(car); carWithPowerLocks(car); 

Après cela, vous pouvez découvrir le coût de la voiture dans une configuration améliorée:

 //       console.log(car.cost()); 

Résumé


Dans cet article, nous avons examiné plusieurs modèles de conception utilisés en JavaScript, mais, en fait, il existe encore de nombreux modèles qui peuvent être utilisés pour résoudre un large éventail de problèmes.

Bien que la connaissance des différents modèles de conception soit importante pour le programmeur, leur utilisation appropriée est tout aussi importante. Connaissant les modèles et la portée de leur application, le programmeur, analysant la tâche qui lui est confiée, peut comprendre quel type de modèle peut aider à le résoudre.

Chers lecteurs! Quels modèles de conception utilisez-vous le plus souvent?

Source: https://habr.com/ru/post/fr427293/


All Articles