Princípios GRASP: entendendo a responsabilidade de objetos
1. Introdução aos Princípios GRASP
Os princípios GRASP (General Responsibility Assignment Software Patterns) são um conjunto de padrões fundamentais para a atribuição de responsabilidades a classes e objetos em sistemas orientados a objetos. Criados por Craig Larman no livro "Applying UML and Patterns", esses princípios fornecem diretrizes claras para responder à pergunta central do design orientado a objetos: "Qual classe deve ser responsável por esta funcionalidade?"
Diferentemente dos padrões de projeto (GoF) que oferecem soluções recorrentes para problemas específicos, os GRASP atuam como princípios de design de mais alto nível, ajudando a decidir onde colocar as responsabilidades. Eles se relacionam diretamente com outros temas da nossa série, como DRY (Don't Repeat Yourself) e YAGNI (You Aren't Gonna Need It), formando uma base sólida para projetos sustentáveis.
2. Information Expert: quem sabe, executa
O princípio Information Expert estabelece que a responsabilidade deve ser atribuída à classe que possui a informação necessária para executá-la. Isso promove alta coesão e baixo acoplamento.
Exemplo prático: Considere um sistema de pedidos. Para calcular o total de um pedido, a classe Pedido é a Information Expert natural, pois possui os itens e seus preços.
Classe Pedido {
- itens: List<ItemPedido>
metodo calcularTotal() {
total = 0
para cada item em itens {
total += item.preco * item.quantidade
}
retornar total
}
}
Classe Cliente {
- nome: String
- pedidos: List<Pedido>
// NÃO deve calcular o total de um pedido específico
}
Ao colocar o cálculo em Pedido, evitamos que Cliente precise conhecer detalhes internos dos itens, mantendo a responsabilidade onde a informação reside.
3. Creator: quem cria os objetos?
O princípio Creator define critérios para decidir qual classe deve instanciar outra. As regras incluem: agregação (uma classe contém outras), composição (parte-todo), registro (uma classe registra instâncias) e uso intensivo (uma classe utiliza outra frequentemente).
Exemplo: Um carrinho de compras deve criar itens de pedido.
Classe CarrinhoCompras {
- itens: List<ItemCarrinho>
metodo adicionarItem(produto, quantidade) {
item = new ItemCarrinho(produto, quantidade)
itens.adicionar(item)
}
}
Classe ItemCarrinho {
- produto: Produto
- quantidade: int
construtor(produto, quantidade) {
this.produto = produto
this.quantidade = quantidade
}
}
Aqui, CarrinhoCompras é o Creator natural porque agrega itens e os utiliza intensivamente.
4. Controller: o intermediário entre UI e lógica
O Controller atua como intermediário entre a interface do usuário e a lógica de negócio, garantindo a separação de camadas. Existem dois tipos principais: controller de fachada (que representa o sistema como um todo) e controller de caso de uso (específico para cada operação).
Aplicação em APIs REST:
// Controller de caso de uso
Classe PedidoController {
- servicoPedido: ServicoPedido
metodo POST /pedidos(dadosPedido) {
pedido = servicoPedido.criarPedido(dadosPedido)
retornar HTTP 201, pedido
}
metodo GET /pedidos/{id} {
pedido = servicoPedido.buscarPorId(id)
retornar HTTP 200, pedido
}
}
O controller não contém lógica de negócio; apenas delega para serviços apropriados.
5. Low Coupling e High Cohesion: os pilares da manutenibilidade
Low Coupling (Baixo Acoplamento): Minimizar dependências entre classes para que mudanças em uma classe impactem minimamente outras.
High Cohesion (Alta Coesão): Cada classe deve ter responsabilidades bem definidas e fortemente relacionadas.
Trade-off prático:
// Baixo acoplamento, mas baixa coesão (ruim)
Classe Utilitario {
metodo calcularFrete(pedido) { ... }
metodo enviarEmail(cliente, mensagem) { ... }
metodo gerarRelatorio(pedidos) { ... }
}
// Alta coesão e baixo acoplamento (ideal)
Classe CalculadoraFrete {
metodo calcular(pedido) { ... }
}
Classe ServicoEmail {
metodo enviar(cliente, mensagem) { ... }
}
Classe GeradorRelatorio {
metodo gerar(pedidos) { ... }
}
6. Polymorphism, Pure Fabrication e Indirection
Polymorphism: Usar herança e interfaces para tratar variações de comportamento sem condicionais explícitas.
Interface MetodoPagamento {
metodo processar(valor)
}
Classe CartaoCredito implementa MetodoPagamento {
metodo processar(valor) {
// lógica específica
}
}
Classe Boleto implementa MetodoPagamento {
metodo processar(valor) {
// lógica específica
}
}
Pure Fabrication: Criar classes artificiais quando não há um Expert natural. Exemplo: repositórios para persistência.
Classe RepositorioPedido {
metodo salvar(pedido: Pedido) { ... }
metodo buscarPorId(id: int): Pedido { ... }
}
Indirection: Introduzir intermediários para reduzir acoplamento. Exemplo: adaptadores para sistemas externos.
Classe AdaptadorPagamentoExterno {
- apiExterna: APIExterna
metodo processarPagamento(valor) {
apiExterna.enviarTransacao(valor)
}
}
7. Protected Variations: isolando pontos de mudança
Protected Variations visa proteger o sistema contra variações futuras, isolando pontos de mudança através de interfaces, polimorfismo e wrappers.
Exemplo: Suporte a múltiplos meios de pagamento.
Interface ProcessadorPagamento {
metodo processar(transacao: Transacao): Resultado
}
Classe SistemaPagamentos {
- processador: ProcessadorPagamento
metodo realizarPagamento(transacao) {
resultado = processador.processar(transacao)
// lógica comum independente do meio de pagamento
}
}
Ao programar para a interface ProcessadorPagamento, o sistema fica protegido contra a adição de novos meios de pagamento.
8. Conclusão e integração com a série
Os 9 princípios GRASP — Information Expert, Creator, Controller, Low Coupling, High Cohesion, Polymorphism, Pure Fabrication, Indirection e Protected Variations — formam uma base sólida para o design orientado a objetos. Eles são interdependentes: aplicar um frequentemente reforça outros.
GRASP se relaciona diretamente com DRY (evitando duplicação de responsabilidades), YAGNI (não criar abstrações desnecessárias) e padrões de projeto (que são soluções concretas baseadas nesses princípios). Por exemplo, o padrão Strategy é uma aplicação direta de Polymorphism e Protected Variations.
Para aplicar GRASP em projetos reais, comece revisando o design existente: cada classe tem uma responsabilidade clara? As dependências são mínimas? Pontos de variação estão isolados? Com a prática, esses princípios se tornam intuitivos, guiando decisões de design que resultam em sistemas mais flexíveis e manuteníveis.
Referências
- Princípios GRASP — DevMedia — Artigo completo explicando cada um dos 9 princípios GRASP com exemplos em Java.
- GRASP: General Responsibility Assignment Software Patterns — Refactoring.Guru — Guia visual com descrições detalhadas e diagramas UML para cada padrão GRASP.
- Applying UML and Patterns — Craig Larman (Amazon) — Livro original de Craig Larman que introduziu os princípios GRASP, referência fundamental sobre o tema.
- GRASP Principles — Martin Fowler — Artigo de Martin Fowler discutindo a importância dos GRASP no design orientado a objetos.
- Padrões GRASP na prática com C# — Medium — Tutorial prático implementando os princípios GRASP em C# com exemplos de código reais.
- GRASP vs GoF: entendendo as diferenças — Alura — Artigo comparando os princípios GRASP com os padrões de projeto GoF, mostrando como se complementam.