Skip to content

Instantly share code, notes, and snippets.

@andersonbuenos
Created September 2, 2025 06:31
Show Gist options
  • Save andersonbuenos/352e593a2c41f4063685f8142481599c to your computer and use it in GitHub Desktop.
Save andersonbuenos/352e593a2c41f4063685f8142481599c to your computer and use it in GitHub Desktop.
Padrões de Projeto (Design Patterns) em Java

Padrões de Projeto (Design Patterns) em Java — o que são, por que usar, como usar e exemplos práticos

  1. O que são
  • São soluções reutilizáveis e testadas para problemas recorrentes de design de software. Não são código pronto, mas modelos de como organizar classes, objetos e responsabilidades.
  • Funcionam como um vocabulário comum entre desenvolvedores (ex.: “use um Strategy”, “isso pede um Observer”).
  • São organizados em categorias:
    • Criacionais: como criar objetos (Factory Method, Abstract Factory, Builder, Singleton, Prototype).
    • Estruturais: como compor classes/objetos (Adapter, Facade, Decorator, Composite, Proxy, Bridge, Flyweight).
    • Comportamentais: como objetos interagem (Strategy, Observer, Command, Template Method, State, Chain of Responsibility, Mediator, Iterator, Visitor, Memento, Interpreter).
  1. Por que usar
  • Manutenção e evolução: código mais claro e extensível (aberto para extensão, fechado para modificação).
  • Reuso de soluções: evita reinventar a roda e reduz risco de erros.
  • Comunicação: facilita alinhamento entre time e revisão de código.
  • Testabilidade: separa responsabilidades, permite mockar/alternar implementações.
  1. Como usar (passo a passo)
  • Identifique o problema recorrente: “Preciso trocar um algoritmo sem mexer no cliente?”, “Preciso padronizar a criação de objetos complexos?”, “Preciso notificar vários interessados automaticamente?”.
  • Escolha um padrão que se encaixe no contexto e nas restrições (tempo, acoplamento, performance).
  • Modele as interfaces e classes seguindo a intenção do padrão (interfaces antes de classes concretas).
  • Implemente incrementalmente e escreva testes para validar o comportamento e permitir troca de implementações.
  • Revise: evite overengineering. Só introduza um padrão quando agrega clareza, flexibilidade ou reduz risco.
  1. Exemplos do dia a dia
  • Strategy: escolher meio de transporte (carro, metrô, bicicleta) conforme trânsito/clima. Você mantém a “interface” de deslocamento e troca a estratégia.
  • Observer: inscrição em notificações de promoções; a loja notifica todos os assinantes quando há oferta.
  • Factory Method: máquina de café que prepara espresso, latte, cappuccino a partir de um “tipo” escolhido.
  • Decorator: pedir um sorvete e adicionar coberturas (calda, castanhas) sem mudar o sorvete base.
  • Facade: concierge do hotel que resolve reservas, táxi e ingressos por você com uma única interface.
  • Adapter: adaptador de tomada que converte pinos para o padrão local.
  • Command: garçom anota pedidos (comando) e a cozinha executa depois, podendo cancelar/repetir.
  • State: catraca do metrô que se comporta diferente conforme o estado (travada, destravada).
  • Chain of Responsibility: suporte ao cliente que escala o chamado do nível 1 para o 2 e assim por diante.
  1. Aplicação em sistemas Java (quando e onde usar)
  • Em APIs e serviços:
    • Strategy: para políticas de desconto, cálculo de frete, seleção de algoritmo (ex.: compressão, criptografia).
    • Factory/Builder: para construir DTOs/entidades complexas, clientes HTTP configuráveis.
    • Facade: para expor uma interface simples a um subsistema complexo (integrações).
    • Decorator/Proxy: para adicionar cross-cutting (cache, retry, logging) sem alterar a lógica principal.
    • Observer/Events: para propagar eventos de domínio (ex.: pedido criado → faturamento/notificação).
  • Em frameworks:
    • Spring usa Singleton como escopo padrão de beans e Proxy para AOP.
    • HandlerAdapter/HandlerMapping em Spring MVC refletem Adapter/Strategy.
    • RestTemplate/WebClient builders refletem Builder.
    • Template Method em classes abstratas do Spring (ex.: AbstractView, AbstractController).
  1. Cinco padrões essenciais com mini-exemplos em Java

6.1) Strategy — troque algoritmos em tempo de execução

  • Quando: você quer variar o “como” sem mudar o “o quê”.
  • Analogia: escolher transporte (carro, metrô, bike) para ir ao trabalho.
interface FreteStrategy {
    double calcular(double pesoKg, String cepDestino);
}

class FreteSedex implements FreteStrategy {
    public double calcular(double pesoKg, String cep) { return 25 + 2.5 * pesoKg; }
}

class FreteEconomico implements FreteStrategy {
    public double calcular(double pesoKg, String cep) { return 10 + 1.2 * pesoKg; }
}

class CalculadoraFrete {
    private FreteStrategy strategy;
    public CalculadoraFrete(FreteStrategy strategy) { this.strategy = strategy; }
    public void setStrategy(FreteStrategy strategy) { this.strategy = strategy; }
    public double calcular(double peso, String cep) { return strategy.calcular(peso, cep); }
}

// Uso
CalculadoraFrete calc = new CalculadoraFrete(new FreteSedex());
double valor = calc.calcular(3.0, "01000-000");
calc.setStrategy(new FreteEconomico());
double valor2 = calc.calcular(3.0, "01000-000");

6.2) Observer — notifique interessados automaticamente

  • Quando: várias partes precisam reagir a uma mudança/evento.
  • Analogia: newsletter/alerta de promoções.
interface PedidoListener {
    void onCriado(Pedido pedido);
}

class Pedido {
    private final List<PedidoListener> listeners = new ArrayList<>();
    public void addListener(PedidoListener l) { listeners.add(l); }
    public void criar() {
        // ... lógica de criação
        listeners.forEach(l -> l.onCriado(this));
    }
}

// Observadores
class NotificadorEmail implements PedidoListener {
    public void onCriado(Pedido pedido) { System.out.println("Email: pedido criado!"); }
}
class Faturamento implements PedidoListener {
    public void onCriado(Pedido pedido) { System.out.println("Fatura gerada."); }
}

// Uso
Pedido p = new Pedido();
p.addListener(new NotificadorEmail());
p.addListener(new Faturamento());
p.criar();

6.3) Factory Method — delegue a criação ao subtipo

  • Quando: subclasses decidem qual objeto concreto instanciar.
  • Analogia: máquina de café que, a partir do tipo, sabe produzir a bebida.
abstract class LoggerCreator {
    public Logger create() {
        Logger logger = createLogger();
        logger.configure(); // gancho comum
        return logger;
    }
    protected abstract Logger createLogger();
}

class FileLoggerCreator extends LoggerCreator {
    protected Logger createLogger() { return new FileLogger("app.log"); }
}

interface Logger { void configure(); void info(String msg); }
class FileLogger implements Logger {
    public FileLogger(String path) {/*...*/}
    public void configure() {/*...*/}
    public void info(String msg) { /* escreve no arquivo */ }
}

// Uso
LoggerCreator creator = new FileLoggerCreator();
Logger logger = creator.create();
logger.info("start");

6.4) Decorator — adicione comportamentos sem herança pesada

  • Quando: você quer “embelezar”/estender recursos dinamicamente.
  • Analogia: sorvete com coberturas extras.
interface Relatorio {
    String gerar();
}

class RelatorioBase implements Relatorio {
    public String gerar() { return "dados"; }
}

abstract class RelatorioDecorator implements Relatorio {
    protected final Relatorio wrappee;
    protected RelatorioDecorator(Relatorio wrappee) { this.wrappee = wrappee; }
}

class RelatorioComTimestamp extends RelatorioDecorator {
    public RelatorioComTimestamp(Relatorio w) { super(w); }
    public String gerar() { return wrappee.gerar() + " | ts=" + System.currentTimeMillis(); }
}

class RelatorioComAssinatura extends RelatorioDecorator {
    public RelatorioComAssinatura(Relatorio w) { super(w); }
    public String gerar() { return wrappee.gerar() + " | assinatura=Equipe"; }
}

// Uso (composições diferentes)
Relatorio r = new RelatorioComAssinatura(new RelatorioComTimestamp(new RelatorioBase()));
System.out.println(r.gerar());

6.5) Facade — simplifique um subsistema complexo

  • Quando: há muitas classes/etapas e você quer oferecer uma entrada única e simples.
  • Analogia: concierge do hotel resolvendo várias tarefas com um único pedido.
class PagamentoService { void pagar() {/*...*/} }
class EstoqueService { void reservar() {/*...*/} }
class EntregaService { void despachar() {/*...*/} }

class PedidoFacade {
    private final PagamentoService pagamento = new PagamentoService();
    private final EstoqueService estoque = new EstoqueService();
    private final EntregaService entrega = new EntregaService();
    public void concluirPedido() {
        pagamento.pagar();
        estoque.reservar();
        entrega.despachar();
    }
}

// Uso
new PedidoFacade().concluirPedido();
  1. Diagrama Mermaid (exemplo do padrão Strategy)
classDiagram
    class CalculadoraFrete {
      -FreteStrategy strategy
      +CalculadoraFrete(FreteStrategy)
      +setStrategy(FreteStrategy) void
      +calcular(peso: double, cep: String) double
    }

    class FreteStrategy {
      <<interface>>
      +calcular(peso: double, cep: String) double
    }

    class FreteSedex {
      +calcular(peso: double, cep: String) double
    }

    class FreteEconomico {
      +calcular(peso: double, cep: String) double
    }

    FreteStrategy <|.. FreteSedex
    FreteStrategy <|.. FreteEconomico
    CalculadoraFrete --> FreteStrategy
Loading
  1. Boas práticas e armadilhas
  • Evite overengineering: não introduza padrões sem necessidade real.
  • Prefira composição a herança quando possível (Decorator, Strategy).
  • Nomeie bem as interfaces/classes para deixar a intenção óbvia.
  • Cubra com testes de unidade e contratuais (interface) para alternar implementações com segurança.
  • Documente o “por que” do padrão no local do código (JavaDoc) para facilitar manutenção.
  • Em Java, use recursos do ecossistema: Spring (AOP/Proxy, Events/Observer), Lombok (Builder), registros (Record) quando for apenas dados.
  1. Checklist rápido para escolher um padrão
  • Preciso variar algoritmo? → Strategy/Template Method.
  • Preciso notificação reativa? → Observer/Eventos.
  • Criação flexível/complexa? → Factory Method/Abstract Factory/Builder.
  • Interface simples para subsistemas complexos? → Facade.
  • Adicionar comportamentos sem herança? → Decorator.
  • Controlar instância única/global? → Singleton (com cuidado).
  • Controlar acesso/remoto/lazy? → Proxy.
  • Encadear responsabilidades? → Chain of Responsibility.
  • Estados alteram comportamento? → State.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment