paint-brush
O que é GRASP? Especialista em informações e princípios do criador em JavaScriptpor@serhiirubets
11,509 leituras
11,509 leituras

O que é GRASP? Especialista em informações e princípios do criador em JavaScript

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

Muito longo; Para ler

GRASP é abreviado como *Padrões de Software de Atribuição de Responsabilidade Geral. É um conjunto de recomendações, princípios e padrões que são realmente bons e podem tornar nosso código muito melhor. Hoje temos muitas práticas recomendadas, princípios (SOLID, DRY, GoF, KISS e outros) e outros, e todos eles estão tentando nos ajudar a escrever um código bom, de fácil manutenção e compreensível. Queremos criar uma funcionalidade que calcule a soma total de nossos itens solicitados. Aprenderemos os 2 primeiros princípios: Especialista em informação e Criador.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - O que é GRASP? Especialista em informações e princípios do criador em JavaScript
Serhii Rubets HackerNoon profile picture

Hoje, temos muitas boas práticas, princípios (SOLID, DRY, KISS), padrões GoF e muito mais.

Eles estão todos tentando nos ajudar, desenvolvedores, a escrever um código bom, limpo, sustentável e compreensível.


GRASP é uma abreviação de General Responsibility Assignment Software Patterns.


É um conjunto de recomendações, princípios e padrões que são realmente bons e podem tornar nosso código muito melhor. Vamos dar uma olhada nesta lista:


  • especialista em informação
  • O Criador
  • Controlador
  • baixo acoplamento
  • Alta Coesão
  • Fabricação Pura
  • Indireção
  • Variações protegidas
  • Polimorfismo


Hoje, aprenderemos os 2 primeiros princípios: Especialista em informações e Criador.

Especialista em Informação

Especialista em informação pode ser o mais importante de todos os padrões GRASP. Esse padrão diz que todos os métodos que trabalham com dados (variáveis, campos) devem estar no mesmo local onde os dados (variáveis ou campos) existem.


Eu sei, eu sei, não parece muito claro, então vamos ver um exemplo. Queremos criar uma funcionalidade que calcule a soma total de nossos itens solicitados.


Vamos imaginar que temos 4 arquivos: main.js, OrderList, OrderItem e Product.


O produto pode conter id, nome e preço (e muitos outros campos, que não são relativos ao nosso exemplo):


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


OrderItem será um objeto simples que contém product e count, conforme abaixo:


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


O arquivo OrderList conterá a lógica para trabalhar com uma matriz de orderItems.


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


E main.js é apenas aquele arquivo que pode conter alguma lógica inicial, pode importar OrderList e fazer algo com essa 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]);


Onde deve ser criado o método que calcula a soma total? Existem pelo menos 2 arquivos, e cada um deles podemos usar para esse fim, certo? Mas qual lugar será melhor para o nosso objetivo?


Vamos pensar em main.js.


Podemos escrever algo como:


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


Vai funcionar. Porém, o arquivo orderItem contém dados sem métodos, o arquivo orderList também contém dados sem método e o arquivo principal contém um método que funciona com itens de pedido e lista de pedidos.


Não soa bem. Se quisermos adicionar mais lógica que funcione com ordens de alguma forma, também a colocaremos no arquivo principal? E, depois de algum tempo, nosso arquivo principal terá muita lógica diferente, para muitos milhares de linhas de código, o que é muito ruim. Esse antipadrão é chamado de objeto de Deus , onde 1 arquivo contém todos.


Como deve ser se quisermos usar uma abordagem de especialista em informações ? Vamos tentar repetir:


Todos os métodos que trabalham com dados (variáveis, campos), devem estar no mesmo local onde existem dados (variáveis ou campos).


Isso significa: orderItem deve conter a lógica que pode calcular uma soma para um item específico:


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


E orderList deve conter uma lógica que possa calcular uma soma total para todos os itens do pedido:


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


E nosso arquivo principal será simples e não conterá lógica para essa funcionalidade; será o mais simples possível (exceto para muitas importações, que removeremos em breve).


Portanto, qualquer lógica relativa a apenas um item do pedido deve ser colocada em orderItem.

Se algo está funcionando relativamente com um conjunto de orderItems, devemos colocar essa lógica em orderItems.


Nosso arquivo principal deve ser apenas um ponto de entrada; faça algumas preparações e importações e conecte algumas lógicas com outras.


Essa separação nos dá um pequeno número de dependências entre os componentes do código, e é por isso que nosso código é muito mais fácil de manter.


Nem sempre podemos usar esse princípio em nosso projeto, mas é um princípio muito bom. E se você pode usá-lo, você deve fazê-lo.

O Criador

Em nosso exemplo anterior, tínhamos 4 arquivos: Main, OrderList, OrderItem e Product. Especialista em informação diz onde os métodos devem estar: no mesmo lugar onde estão os dados.


Mas a questão é: quem e onde os objetos devem ser criados? Quem criará orderList, quem criará orderItem, quem criará Product?


O criador diz que cada objeto (classe) deve ser criado apenas no local onde será utilizado. Lembra do nosso exemplo no arquivo principal com muitas importações? Vamos checar:


 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, quase todas as importações e criações de objetos estão em main.js.


Mas, vamos pensar em quem e onde é realmente usado.


O produto é usado apenas em OrderItem. OrderItem é usado apenas em OrderList. OrderList é usado em Main. Se parece com isso:


Principal → OrderList → OrderItem → Produto


Mas se Main usa apenas OrderList, por que criamos OrderItem em Main? Por que também criamos um Produto aqui? Por enquanto, nosso Main.js cria (e importa) quase tudo. É mau.


Seguindo o princípio do Criador , devemos criar objetos apenas nos locais onde esses objetos são usados. Imagine que, usando nosso aplicativo, adicionamos produtos ao carrinho. Isto é o que poderia parecer:


Main.js: Criamos (e importamos) apenas OrderList aqui:


 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: Criamos (e importamos) apenas OrderItem aqui:


 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: Criamos (e importamos) apenas o produto aqui:


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


Produto.js:


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


Temos uma dependência simples:


Principal → OrderList → OrderItem → Produto


E agora, cada objeto cria apenas naquele lugar, onde é usado. Isso é o que diz o princípio do Criador .


Espero que esta introdução seja útil para você e, na próxima série do GRASP, abordaremos outros princípios.


Foto de Gabriel Heinzer no Unsplash