paint-brush
Qu'est-ce que GRASP ? Principes de l'expert en information et du créateur en JavaScriptpar@serhiirubets
12,126 lectures
12,126 lectures

Qu'est-ce que GRASP ? Principes de l'expert en information et du créateur en JavaScript

par Serhii Rubets1m2022/06/07
Read on Terminal Reader
Read this story w/o Javascript

Trop long; Pour lire

GRASP est abrégé en *Modèles logiciels d'affectation de responsabilité générale. C'est un ensemble de recommandations, de principes et de modèles qui sont vraiment bons et qui pourraient rendre notre code bien meilleur. Aujourd'hui, nous avons de nombreuses meilleures pratiques, principes (SOLID, DRY, GoF, KISS et autres) et autres, et ils essaient tous de nous aider, d'écrire un bon code maintenable et compréhensible. Nous voulons créer une fonctionnalité qui calculera la somme totale de nos articles commandés. Nous apprendrons les 2 premiers principes : Expert de l'information et Créateur.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Qu'est-ce que GRASP ? Principes de l'expert en information et du créateur en JavaScript
Serhii Rubets HackerNoon profile picture

Aujourd'hui, nous avons beaucoup de bonnes pratiques, de principes (SOLID, DRY, KISS), de modèles GoF, et plus encore.

Ils essaient tous de nous aider, nous développeurs, à écrire un bon code, propre, maintenable et compréhensible.


GRASP est l'abréviation de General Responsibility Assignment Software Patterns.


C'est un ensemble de recommandations, de principes et de modèles qui sont vraiment bons et qui pourraient rendre notre code bien meilleur. Jetons un œil à cette liste :


  • Spécialiste de l'information
  • Créateur
  • Manette
  • Couplage bas
  • Haute Cohésion
  • Fabrication pure
  • Indirection
  • Variantes protégées
  • Polymorphisme


Aujourd'hui, nous allons apprendre les 2 premiers principes : Expert de l'information et Créateur.

Spécialiste de l'information

L'expert en information pourrait être le plus important de tous les modèles GRASP. Ce modèle indique que toutes les méthodes qui fonctionnent avec des données (variables, champs) doivent se trouver au même endroit où les données (variables ou champs) existent.


Je sais, je sais, cela ne semble pas si clair, alors voyons un exemple. Nous voulons créer une fonctionnalité qui calculera la somme totale de nos articles commandés.


Imaginons que nous ayons 4 fichiers : main.js, OrderList, OrderItem et Product.


Le produit peut contenir un identifiant, un nom et un prix (et de nombreux autres champs, qui ne sont pas relatifs à notre exemple) :


 class Product { constructor(name, price) { this.name = name; this.price = price; } }


OrderItem sera un objet simple contenant le produit et le nombre, comme ci-dessous :


 class OrderItem { constructor(product, count) { this.product = product, this.count = count } }


Le fichier OrderList contiendra la logique pour fonctionner avec un tableau d'articles de commande.


 class OrderList { constructor(items) { this.items = items; } }


Et, main.js est juste ce fichier qui pourrait contenir une logique initiale, peut importer OrderList et faire quelque chose avec cette liste.


 import { OrderItem } from './OrderItem'; import { OrderList } from './OrderList'; import { Product } from './Product'; const samsung = new Product('Samsung', 200); const apple = new Product('Apple', 300); const lg = new Product('Lg', 150); const samsungOrder = new OrderItem(samsung, 2); const appleOrder = new OrderItem(samsung, 3); const lgOrder = new OrderItem(samsung, 4); const orderList = new OrderList([samsungOrder, appleOrder, lgOrder]);


Où la méthode qui calcule la somme totale doit-elle être créée ? Il y a au moins 2 fichiers, et chacun d'eux peut être utilisé à cette fin, n'est-ce pas ? Mais quel endroit sera le meilleur pour notre objectif ?


Pensons à main.js.


Nous pouvons écrire quelque chose comme :


 const totalSum = orderList.reduce((res, order) => { return res + order.product.price * order.count }, 0)


Ça va marcher. Mais, le fichier orderItem contient des données sans méthodes, le fichier orderList contient également des données sans méthode et le fichier principal contient une méthode qui fonctionne avec les articles de commande et la liste de commandes.


Ça ne sonne pas bien. Si nous voulons ajouter plus de logique qui fonctionne avec les commandes d'une manière ou d'une autre, allons-nous également la mettre dans le fichier principal ? Et, après un certain temps, notre fichier principal aura beaucoup de logique différente, pour plusieurs milliers de lignes de code, ce qui est vraiment mauvais. Cet antipattern est appelé God object , où 1 fichier contient tout.


Comment devrait-il en être si nous voulons utiliser une approche d'expert en information ? Essayons de répéter :


Toutes les méthodes qui fonctionnent avec des données (variables, champs) doivent se trouver au même endroit où les données (variables ou champs) existent.


Cela signifie : orderItem doit contenir une logique permettant de calculer une somme pour un élément spécifique :


 class OrderItem { constructor(product, count) { this.product = product, this.count = count } getTotalPrice() { return this.product.price * this.count; } }


Et orderList doit contenir une logique capable de calculer une somme totale pour tous les éléments de la commande :


 class OrderList { constructor(items) { this.items = items; } getTotalPrice() { return this.items.reduce((res, item) => { return res + item.getTotalPrice(); }, 0); } }


Et, notre fichier principal sera simple et ne contiendra pas de logique pour cette fonctionnalité ; ce sera le plus simple possible (sauf pour de nombreux imports, que nous supprimerons prochainement).


Ainsi, toute logique relative à un seul article de commande doit être placée dans orderItem.

Si quelque chose fonctionne relativement avec un ensemble d'éléments de commande, nous devrions mettre cette logique dans les éléments de commande.


Notre fichier principal ne doit être qu'un point d'entrée ; faire de la préparation et des importations, et connecter une logique avec d'autres.


Cette séparation nous donne un petit nombre de dépendances entre les composants de code, et c'est pourquoi notre code est beaucoup plus maintenable.


Nous ne pouvons pas toujours utiliser ce principe dans notre projet, mais c'est un très bon principe. Et si vous pouvez l'utiliser, vous devriez le faire.

Créateur

Dans notre exemple précédent, nous avions 4 fichiers : Main, OrderList, OrderItem et Product. L'expert en information dit où les méthodes devraient être : au même endroit où se trouvent les données.


Mais la question est : qui et où les objets doivent-ils être créés ? Qui créera orderList, qui créera orderItem, qui créera Product ?


Le créateur dit que chaque objet (classe) doit être créé uniquement à l'endroit où il sera utilisé. Vous souvenez-vous de notre exemple dans le fichier principal avec de nombreuses importations ? Allons vérifier:


 import { OrderItem } from './OrderItem'; import { OrderList } from './OrderList'; import { Product } from './Product'; const samsung = new Product('Samsung', 200); const apple = new Product('Apple', 300); const lg = new Product('Lg', 150); const samsungOrder = new OrderItem(samsung, 2); const appleOrder = new OrderItem(samsung, 3); const lgOrder = new OrderItem(samsung, 4); const orderList = new OrderList([samsungOrder, appleOrder, lgOrder]); const totalSum = orderList.getTotalPrice();


Comme nous pouvons le voir, presque toutes les importations et créations d'objets sont dans main.js.


Mais réfléchissons à qui et où il est vraiment utilisé.


Le produit est uniquement utilisé dans OrderItem. OrderItem est uniquement utilisé dans OrderList. OrderList est utilisé sur Main. Il ressemble à ceci :


Principal → Liste de commandes → Article de commande → Produit


Mais si Main utilise uniquement OrderList, pourquoi créons-nous OrderItem dans Main ? Pourquoi créons-nous également un produit ici ? Pour le moment, notre Main.js crée (et importe) presque tout. C'est mauvais.


Suivant le principe du Créateur , nous devrions créer des objets uniquement dans les endroits où ces objets sont utilisés. Imaginez qu'en utilisant notre application, nous ayons ajouté des produits au panier. Voici à quoi cela pourrait ressembler :


Main.js : Nous créons (et importons) uniquement OrderList ici :


 import { OrderList } from './OrderList'; const cartProducts = [{ name: 'Samsung', price: 200, count: 2 }, { name: 'Apple', price: 300, count: 3 }, {name: 'Lg', price: 150, count: 4 }]; const orderList = new OrderList(cartProducts); const totalPrice = orderList.getTotalPrice();


OrderList.js : nous créons (et importons) uniquement OrderItem ici :


 import { OrderItem } from './OrderItem'; class OrderList { constructor(items) { this.items = items.map(item => new OrderItem(item)); } getTotalPrice() { return this.items.reduce((res, item) => { return res + item.getPrice(); }, 0); } }


OrderItem.js : nous créons (et importons) uniquement des produits ici :


 import { Product } from './Product'; class OrderItem { constructor(item) { this.product = new Product(item.name, item.price); this.count = item.count; } }


Produit.js :


 class Product { constructor(name, price) { this.name = name; this.price = price; } }


Nous avons une simple dépendance :


Principal → Liste de commandes → Article de commande → Produit


Et maintenant, chaque objet ne crée qu'à cet endroit, là où il est utilisé. C'est ce que dit le principe du Créateur .


J'espère que cette introduction vous sera utile, et dans la prochaine série de GRASP, nous couvrirons d'autres principes.


Photo de Gabriel Heinzer sur Unsplash