paint-brush
¿Qué es GRASP? Experto en información y principios del creador en JavaScriptpor@serhiirubets
11,509 lecturas
11,509 lecturas

¿Qué es GRASP? Experto en información y principios del creador en JavaScript

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

Demasiado Largo; Para Leer

GRASP se abrevia como *Patrones de software de asignación de responsabilidad de responsabilidad general. Es un conjunto de recomendaciones, principios y patrones que son realmente buenos y podrían mejorar mucho nuestro código. Hoy tenemos muchas mejores prácticas, principios (SÓLIDO, SECO, GoF, KISS y otros) y otros, y todos intentan ayudarnos a escribir código bueno, fácil de mantener y comprensible. Queremos crear una funcionalidad que calcule la suma total de nuestros artículos pedidos. Aprenderemos los 2 primeros principios: Experto en información y Creador.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - ¿Qué es GRASP? Experto en información y principios del creador en JavaScript
Serhii Rubets HackerNoon profile picture

Hoy en día, tenemos muchas buenas prácticas, principios (SÓLIDO, SECO, KISS), patrones GoF y más.

Todos están tratando de ayudarnos a nosotros, los desarrolladores, a escribir un código bueno, limpio, fácil de mantener y comprensible.


GRASP es una abreviatura de Patrones de software de asignación de responsabilidad general.


Es un conjunto de recomendaciones, principios y patrones que son realmente buenos y podrían mejorar mucho nuestro código. Echemos un vistazo a esta lista:


  • experto en información
  • Creador
  • Controlador
  • Acoplamiento bajo
  • Alta Cohesión
  • Fabricación pura
  • Indirección
  • Variaciones protegidas
  • Polimorfismo


Hoy aprenderemos los 2 primeros principios: Experto en información y Creador.

Experto en información

Experto en información podría ser el más importante de todos los patrones GRASP. Este patrón dice que todos los métodos que trabajan con datos (variables, campos), deben estar en el mismo lugar donde existen los datos (variables o campos).


Lo sé, lo sé, no suena tan claro, así que veamos un ejemplo. Queremos crear una funcionalidad que calcule la suma total de nuestros artículos pedidos.


Imaginemos que tenemos 4 archivos: main.js, OrderList, OrderItem y Product.


El producto podría contener id, nombre y precio (y muchos otros campos, que no son relativos a nuestro ejemplo):


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


OrderItem será un objeto simple que contiene el producto y el recuento, como se muestra a continuación:


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


El archivo OrderList contendrá lógica para trabajar con una matriz de artículos de pedido.


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


Y main.js es solo ese archivo que podría contener algo de lógica inicial, puede importar OrderList y hacer algo con esta lista.


 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]);


¿Dónde se debe crear el método que calcula la suma total? Hay al menos 2 archivos, y cada uno de ellos podemos usarlo para este propósito, ¿verdad? Pero, ¿qué lugar será mejor para nuestro objetivo?


Pensemos en main.js.


Podemos escribir algo como:


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


Funcionará. Sin embargo, el archivo orderItem contiene datos sin métodos, el archivo orderList también contiene datos sin método y el archivo principal contiene un método que funciona con artículos de pedidos y listas de pedidos.


No suena bien. Si queremos agregar más lógica que funcione con órdenes de alguna manera, ¿también la pondremos en el archivo principal? Y, después de un tiempo, nuestro archivo principal tendrá mucha lógica diferente, para muchos miles de líneas de código, lo cual es realmente malo. Este antipatrón se llama objeto Dios , donde 1 archivo contiene todo.


¿Cómo debería ser si queremos utilizar un enfoque experto en información ? Intentemos repetir:


Todos los métodos que trabajan con datos (variables, campos), deben estar en el mismo lugar donde existen los datos (variables o campos).


Esto significa: orderItem debe contener una lógica que pueda calcular una suma para un artículo específico:


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


Y orderList debe contener una lógica que pueda calcular una suma total para todos los artículos del pedido:


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


Y nuestro archivo principal será simple y no contendrá lógica para esa funcionalidad; será lo más simple posible (a excepción de muchas importaciones, que eliminaremos pronto).


Por lo tanto, cualquier lógica, que sea relativa a un solo artículo de pedido, debe colocarse en orderItem.

Si algo funciona relativamente con un conjunto de artículos de pedido, deberíamos poner esa lógica en artículos de pedido.


Nuestro archivo principal solo debe ser un punto de entrada; hacer algo de preparación, e importaciones, y conectar unas lógicas con otras.


Esta separación nos da una pequeña cantidad de dependencias entre los componentes del código, y es por eso que nuestro código es mucho más fácil de mantener.


No siempre podemos usar este principio en nuestro proyecto, pero es un muy buen principio. Y si puedes usarlo, debes hacerlo.

Creador

En nuestro ejemplo anterior, teníamos 4 archivos: Main, OrderList, OrderItem y Product. El experto en información dice dónde deben estar los métodos: en el mismo lugar donde están los datos.


Pero la pregunta es: ¿quién y dónde deben crearse los objetos? ¿Quién creará orderList, quién creará orderItem, quién creará Product?


El creador dice que cada objeto (clase) debe crearse solo en el lugar donde se utilizará. ¿Recuerda nuestro ejemplo en el archivo principal con muchas importaciones? Vamos a revisar:


 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();


Como podemos ver, casi todas las importaciones y creaciones de objetos están en main.js.


Pero, pensemos en quién y dónde se usa realmente.


El producto solo se utiliza en OrderItem. OrderItem solo se usa en OrderList. OrderList se usa en Main. Se parece a esto:


Principal → Lista de pedidos → Artículo de pedido → Prodcut


Pero si Main solo usa OrderList, ¿por qué creamos OrderItem en Main? ¿Por qué también creamos un Producto aquí? Por el momento, nuestro Main.js crea (e importa) casi todo. Es malo.


Siguiendo el principio del Creador , debemos crear objetos solo en los lugares donde se usan estos objetos. Imagina que, usando nuestra app, añadimos productos al carrito. Así es como podría verse:


Main.js: creamos (e importamos) solo OrderList aquí:


 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: Creamos (e importamos) solo OrderItem aquí:


 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: Creamos (e importamos) solo Producto aquí:


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


Producto.js:


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


Tenemos una dependencia simple:


Principal → Lista de pedidos → Artículo de pedido → Producto


Y ahora, cada objeto crea solo en ese lugar, donde se usa. Eso es lo que dice el principio del Creador .


Espero que esta introducción le sea útil, y en la próxima serie de GRASP, cubriremos otros principios.


Foto de Gabriel Heinzer en Unsplash