Padrões GoF Comportamentais: Desacoplando Algoritmos e Reações com Strategy e Observer
Como utilizar os padrões clássicos Strategy e Observer para intercambiar algoritmos em tempo de execução e construir fluxos reativos e desacoplados.
Neste artigo
Publicados em 1994 pelo grupo conhecido como Gang of Four (GoF), os 23 padrões de projeto clássicos continuam fornecendo soluções estruturadas para desafios recorrentes de engenharia de software. Entre eles, os padrões comportamentais ocupam um papel central, pois concentram-se na atribuição de responsabilidades entre objetos e na forma como algoritmos e fluxos de comunicação operam em tempo de execução.
Dois desses padrões destacam-se pela frequência com que são aplicados em arquiteturas modernas: o Strategy, que permite intercambiar famílias de algoritmos dinamicamente, e o Observer, que estabelece a base teórica de praticamente toda a programação reativa e baseada em eventos no desenvolvimento web e backend.
Strategy: Intercambiabilidade de Algoritmos sem Estruturas Condicionais
O padrão Strategy tem como objetivo encapsular uma família de algoritmos afins, tornando-os intercambiáveis em tempo de execução. Em vez de uma classe acumular blocos condicionais extensos para decidir qual lógica de cálculo ou exportação executar, o algoritmo é extraído para objetos independentes que implementam um contrato comum.
flowchart TD
Contexto["<b>Contexto (Pedido)</b><br/>Mantém referência para a estratégia ativa"]
Interface["<b><<interface>> ICalculoFreteStrategy</b><br/>+ Calcular(peso, distancia): number"]
Sedex["<b>FreteSedexStrategy</b><br/>Calcula via peso e cubagem"]
Transp["<b>FreteTransportadoraStrategy</b><br/>Calcula via tabela rodoviária"]
Contexto -->|"Invoca método Calcular()"| Interface
Interface -.->|"Implementado por"| Sedex
Interface -.->|"Implementado por"| Transp
Implementação Prática
Considere o cálculo dinâmico de fretes em um e-commerce, onde novas transportadoras e promoções são adicionadas periodicamente:
// 1. Contrato da Estratégia
export interface ShippingStrategy {
calculate(weightKg: number, distanceKm: number): number;
}
// 2. Implementações concretas de cada algoritmo
export class ExpressShipping implements ShippingStrategy {
calculate(weightKg: number, distanceKm: number): number {
return 25.0 + (weightKg * 1.8) + (distanceKm * 0.15);
}
}
export class StandardShipping implements ShippingStrategy {
calculate(weightKg: number, distanceKm: number): number {
return 10.0 + (weightKg * 0.9) + (distanceKm * 0.05);
}
}
// 3. Contexto que recebe a estratégia via injeção ou composição
export class OrderCheckout {
private strategy: ShippingStrategy;
constructor(initialStrategy: ShippingStrategy) {
this.strategy = initialStrategy;
}
setShippingStrategy(newStrategy: ShippingStrategy): void {
this.strategy = newStrategy;
}
processTotal(cartValue: number, weightKg: number, distanceKm: number): number {
const shipping = this.strategy.calculate(weightKg, distanceKm);
return cartValue + shipping;
}
}
Essa abordagem atende com rigor ao princípio Aberto/Fechado (Open/Closed): quando a empresa fechar parceria com um novo serviço de entrega por drones ou armários inteligentes, basta criar uma nova classe implementando ShippingStrategy, sem alterar ou colocar em risco a classe OrderCheckout.
Observer: A Base da Reatividade e da Propagação de Eventos
O padrão Observer define uma dependência de um para muitos entre objetos, de forma que quando um objeto (o Subject ou Sujeito) sofre uma alteração de estado, todos os seus dependentes (os Observers ou Observadores) são notificados e atualizados automaticamente.
flowchart TD
Sujeito["<b>Sujeito (EstoqueItem)</b><br/>Mantém lista de observadores: IObserver[]"]
Interface["<b><<interface>> IEstoqueObserver</b><br/>+ Notificar(item, novoSaldo): void"]
Alerta["<b>AlertaComprasObserver</b><br/>Emite pedido de reposição aos fornecedores"]
Auditoria["<b>LogAuditoriaObserver</b><br/>Grava histórico de mutação em storage seguro"]
Sujeito -->|"Dispara Notificar() para todos os assinantes"| Interface
Interface -.->|"Implementado por"| Alerta
Interface -.->|"Implementado por"| Auditoria
O Sujeito desconhece a lógica concreta dos observadores: ele apenas mantém uma lista de assinantes interessados e executa um método de notificação quando uma mutação ocorre.
Da Origem GoF à Reatividade Moderna
O conceito do Observer transcendeu o design clássico de classes e tornou-se a espinha dorsal de importantes bibliotecas e paradigmas contemporâneos:
- RxJS e Streams: O objeto
Observableatua como o sujeito produtor de dados, enquanto operadores e assinantes (Subscriber) funcionam como observadores que reagem a cada nova emissão. - Signals (Angular, Vue, Solid): O grafo reativo de dependências do compilador rastreia automaticamente quais funções de leitura dependem de quais variáveis reativas, disparando recálculos apenas quando os valores observados sofrem mutação real.
- Event-Driven Architecture: Em nível sistêmico, o corretor de mensagens (Kafka ou RabbitMQ) estende o padrão Observer para redes distribuídas, onde tópicos representam sujeitos e serviços consumidores funcionam como observadores desacoplados.
Comparativo: Strategy versus Observer
Embora ambos façam parte do grupo comportamental do GoF, seus objetivos arquiteturais atendem a diferentes momentos da execução:
| Critério | Strategy | Observer |
|---|---|---|
| Intenção Principal | Variar a execução de um algoritmo interno. | Notificar múltiplos agentes sobre uma alteração de estado. |
| Cardinalidade | Um para um (o contexto possui uma estratégia ativa). | Um para muitos (um sujeito notifica dezenas de observadores). |
| Direção do Fluxo | O contexto invoca a estratégia para obter um resultado direto. | O sujeito despacha notificações sem esperar retornos dos ouvintes. |
| Conhecimento | O contexto conhece o contrato e os parâmetros que entrega à estratégia. | O sujeito não se importa com o que os ouvintes farão com a informação. |
Dominar a combinação entre Strategy e Observer permite que o engenheiro de software projete aplicações flexíveis e resistentes a mudanças de escopo, substituindo estruturas rígidas por colaboração dinâmica de objetos coesos.