Design Patterns na Prática - Guia Completo com TypeScript
Se você já se pegou reescrevendo código parecido pela terceira vez, ou lutando para entender aquela classe gigante que faz "tudo", provavelmente está na hora de conhecer Design Patterns. Não como teoria acadêmica distante, mas como ferramentas práticas que vão fazer você escrever código melhor desde amanhã.
Recentemente, atualizei minha wiki pessoal com uma coleção completa de design patterns em TypeScript, com exemplos práticos (e alguns com tema de Pokémon, porque sim 🎮). Neste post, vou te dar um overview geral do que são esses padrões e quando usar cada um. Para implementações completas e código pronto para rodar, a wiki tem tudo detalhado.
O Que São Design Patterns?
Design Patterns são soluções reutilizáveis para problemas comuns no desenvolvimento de software. Pense neles como "receitas" testadas e aprovadas pela comunidade ao longo de décadas. Não são código pronto para copiar e colar, mas sim abordagens de design que você adapta ao seu contexto.
O livro "Gang of Four" (GoF), de 1994, catalogou 23 padrões que até hoje são referência. E o mais legal? Eles continuam relevantes mesmo com TypeScript, React, e tecnologias modernas.
As Três Categorias
Os padrões se dividem em três grupos, cada um resolvendo um tipo diferente de problema:
1. Padrões Criacionais
Lidam com a criação de objetos de forma controlada e flexível. Em vez de usar new direto e criar dependências duras, esses padrões te dão controle sobre como e quando objetos são criados.
Quando usar:
- Você tem lógica complexa de criação
- Precisa controlar quantas instâncias existem
- Quer desacoplar a criação do uso
Padrões que documentei:
- Singleton: Garante uma única instância (ex: config global, pool de conexões)
- Factory Method: Delega a criação de objetos para subclasses
- Abstract Factory: Cria famílias de objetos relacionados
2. Padrões Estruturais
Focam em como classes e objetos são compostos. Ajudam a montar estruturas maiores mantendo flexibilidade e eficiência.
Quando usar:
- Precisa integrar código legado com novo
- Quer adicionar comportamento sem modificar código existente
- Precisa simplificar interfaces complexas
Padrões que documentei:
- Adapter: Adapta uma interface para outra (ex: integrar API antiga com código novo)
- Decorator: Adiciona comportamento dinamicamente (sem herança pesada)
- Facade: Simplifica uma interface complexa
3. Padrões Comportamentais
Tratam de comunicação e responsabilidades entre objetos. Como eles interagem e distribuem trabalho.
Quando usar:
- Tem lógica condicional complexa (
if/elsegigantes) - Precisa notificar múltiplos objetos de mudanças
- Quer encapsular comportamentos como objetos
Padrões que documentei:
- Strategy: Escolhe algoritmos em runtime (ex: diferentes formas de calcular frete)
- Observer: Notifica múltiplos objetos de mudanças (base do padrão pub/sub)
- Command: Encapsula requisições como objetos (útil para undo/redo)
- State: Muda comportamento baseado no estado interno
Padrões na Prática: Exemplos Rápidos
Vou mostrar brevemente alguns casos de uso reais. Para código completo e runnable, confere a wiki.
Strategy: Diferentes Formas de Pagar
// Em vez de if/else gigantes:
class PaymentProcessor {
process(method: string, amount: number) {
if (method === 'credit') {
// lógica cartão
} else if (method === 'pix') {
// lógica pix
} else if (method === 'boleto') {
// lógica boleto
}
}
}
// Use Strategy:
interface PaymentStrategy {
pay(amount: number): void;
}
class CreditCardPayment implements PaymentStrategy {
pay(amount: number) { /* ... */ }
}
class PixPayment implements PaymentStrategy {
pay(amount: number) { /* ... */ }
}
class Checkout {
constructor(private strategy: PaymentStrategy) {}
processPayment(amount: number) {
this.strategy.pay(amount);
}
}
Agora adicionar novos métodos de pagamento não mexe no código existente. Implementação completa na wiki.
Observer: Sistema de Notificações
// Quando algo muda, vários objetos precisam saber
class OrderStatusSubject {
private observers: Observer[] = [];
attach(observer: Observer) {
this.observers.push(observer);
}
notify(order: Order) {
this.observers.forEach(obs => obs.update(order));
}
}
// Email, SMS, push notification - todos observam a mesma mudança
const orderStatus = new OrderStatusSubject();
orderStatus.attach(new EmailNotifier());
orderStatus.attach(new SMSNotifier());
orderStatus.attach(new PushNotifier());
// Uma mudança, três notificações
orderStatus.notify(newOrder);
Base do padrão pub/sub e do Redux, por exemplo. Código completo aqui.
Factory Method: Criando Diferentes Tipos
Imagine um sistema de RPG onde você cria diferentes tipos de personagens:
abstract class CharacterFactory {
abstract createCharacter(): Character;
startGame() {
const character = this.createCharacter();
character.render();
return character;
}
}
class WarriorFactory extends CharacterFactory {
createCharacter() {
return new Warrior();
}
}
class MageFactory extends CharacterFactory {
createCharacter() {
return new Mage();
}
}
Cada factory cria um tipo específico, mas a lógica de início de jogo é compartilhada. Implementação com tema Pokémon na wiki 🎮.
Quando NÃO Usar Design Patterns
Design patterns são ferramentas, não objetivos. Alguns sinais de que você está forçando:
- Overengineering: Você cria 5 classes para algo que seria 10 linhas simples
- Padrão errado: Forçar um padrão que não resolve o problema real
- Complexidade prematura: Adicionar abstrações "para o futuro" que nunca chega
A regra é simples: use quando o problema aparecer, não antes. Refatoração existe pra isso.
Por Que TypeScript?
Todos os exemplos na wiki estão em TypeScript por algumas razões:
- Type safety: Interfaces e tipos tornam os padrões mais explícitos
- Documentação viva: Os tipos servem como documentação automática
- Refatoração segura: Mudar um padrão é mais confiável com tipos
- Ecossistema moderno: Funciona com React, Node, e todo stack moderno
Próximos Passos
Se você quer aprofundar:
- Explore a wiki pessoal - Cada padrão tem implementação completa, testes e explicação detalhada
- Clone e rode: Todos os exemplos são código real rodando com testes
- Pratique refatoração: Pegue código existente e identifique onde um padrão ajudaria
- Não decore, entenda: Foque no problema que cada padrão resolve
Design patterns não vão magicamente fazer seu código perfeito. Mas vão te dar um vocabulário e ferramentas para resolver problemas comuns de forma elegante e testada. E quando alguém falar "usa um Observer aqui", você vai saber exatamente do que estão falando.
Happy coding! 🚀
Recursos:
- Wiki Completa de Design Patterns (com todos os padrões documentados)
- Repositório com exemplos (código executável)