Padrões de Projeto Gang of Four (GoF) em Java
Voltar para Artigos
GoFArtigo

Arquitetura de software

Padrões de Projeto Gang of Four (GoF) em Java

Guia completo dos 23 padrões, exemplos práticos, SOLID e arquitetura do mundo real

Você não vai memorizar 23 nomes: vai aprender a reconhecer os problemas de projeto que tornam um padrão útil.

Documento de código central conectado a estruturas de padrões de projeto GoF em estilo neon

O que este guia promete

Você não vai memorizar 23 nomes. Você vai aprender a reconhecer os problemas de projeto recorrentes que tornam um padrão útil - e as situações em que um padrão apenas deixaria o código pior.

Edição ampliada baseada no manuscrito original de GoF/Java.

Nota editorial

Este artigo usa o manuscrito original como espinha dorsal editorial e o amplia com contexto adicional de engenharia de software em geral. A estrutura original - história, os 23 padrões da Gang of Four, os padrões Java mais encontrados na prática, aprofundamentos, relações com SOLID, exemplos de frameworks, alertas sobre superengenharia, modelos mentais e guias de decisão - foi preservada em espírito, enquanto a prosa foi reescrita para um público internacional amplo.

Postura do leitor

Sempre que possível, olhe para o problema antes do padrão. Pergunte o que está mudando, o que está acoplado, o que está duplicado e o que deveria permanecer estável. O nome do padrão deve vir depois.

Sumário

  1. Introdução: por que o projeto importa mais do que código que funciona
  2. Da arquitetura ao software: a história por trás dos padrões de projeto
  3. O que um padrão de projeto realmente é - e o que ele não é
  4. As três famílias GoF
  5. O catálogo completo dos 23 padrões GoF
  6. Os padrões que você mais vai encontrar em Java
  7. Aprofundamentos com exemplos em Java
  8. Como reconhecer padrões em código existente
  9. Padrões GoF e princípios SOLID
  10. Onde os padrões aparecem no ecossistema Java
  11. Comparações de padrões que evitam erros comuns
  12. Aplicação prática: refatorando um checkout de e-commerce
  13. Superengenharia: quando não usar um padrão
  14. Guia de decisão e matriz de seleção de padrões
  15. Conclusão

1. Introdução: por que o projeto importa mais do que código que funciona

Imagine dois sistemas que passam nos mesmos testes e atendem igualmente aos requisitos de negócio de hoje. No primeiro, cada novo meio de pagamento obriga os desenvolvedores a alterar um condicional longo, mexer em vários controladores, atualizar testes em módulos não relacionados e torcer para que uma integração antiga não quebre. No segundo, um novo meio de pagamento é introduzido acrescentando uma implementação atrás de uma interface estável. O resultado de negócio pode ser idêntico hoje. A diferença fica visível amanhã.

Essa diferença é projeto. Um bom projeto não trata de deixar o código sofisticado. Trata de organizar responsabilidades e dependências de modo que a mudança inevitável tenha custo controlado. Os padrões de projeto GoF se tornaram importantes porque deram aos desenvolvedores um vocabulário comum para estruturas que resolvem repetidamente esse tipo de problema. Um desenvolvedor pode dizer “Strategy”, “Adapter” ou “Facade” e comunicar uma intenção de projeto que, de outro modo, exigiria parágrafos de explicação.

Um padrão de projeto, portanto, não é uma biblioteca, um framework ou um trecho de código para copiar. É uma forma reutilizável de pensar sobre um problema de projeto recorrente. O mesmo padrão pode ter aparência diferente em Java, C#, Python ou TypeScript porque o padrão vive no nível das responsabilidades e da colaboração, não no nível da sintaxe.

Uma pergunta útil para carregar ao longo deste artigo

Se o seu sistema receber cinco novas variações da mesma regra de negócio no mês que vem, quais classes vão precisar mudar? A resposta costuma revelar se um padrão poderia ajudar.

Para o desenvolvedor individual, aprender padrões melhora a leitura de código, a refatoração, a comunicação técnica, as entrevistas, as discussões arquiteturais e a capacidade de entender frameworks que, sem esse repertório, parecem “mágicos”. Para equipes e organizações, o benefício é maior: um vocabulário de projeto compartilhado reduz ambiguidade, melhora a manutenibilidade, ajuda a isolar dependências externas e torna sistemas de vida longa mais fáceis de evoluir.

Há também uma dimensão social mais ampla. A sociedade moderna depende de software em bancos, saúde, transporte, comunicação, governo, educação e infraestrutura. Software manutenível é mais fácil de testar, mais seguro de mudar e menos caro de manter vivo. Padrões de projeto não garantem bom software, mas o pensamento disciplinado por trás deles pode contribuir para sistemas digitais mais confiáveis e práticas de engenharia mais sustentáveis.

2. Da arquitetura ao software: a história por trás dos padrões de projeto

A ideia de “padrão” não começou no software. O arquiteto Christopher Alexander e seus colaboradores descreveram soluções recorrentes para problemas recorrentes de projeto no ambiente construído. A ideia central não era prescrever uma planta rígida, mas capturar uma relação entre contexto, problema, forças e solução, para que profissionais pudessem reutilizar experiência acumulada sem copiar um edifício literalmente.

Engenheiros de software reconheceram uma analogia poderosa. Sistemas orientados a objetos também enfrentavam forças recorrentes: a criação de objetos deveria ser flexível, algoritmos deveriam ser substituíveis, interfaces incompatíveis deveriam cooperar, subsistemas complexos deveriam ser simplificados e objetos deveriam se comunicar sem se enredar em uma teia de dependências.

Em 1994, Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides publicaram Design Patterns: Elements of Reusable Object-Oriented Software. Por causa dos quatro autores, o livro ficou conhecido como o livro da Gang of Four, ou GoF. Sua contribuição duradoura não foi ter inventado cada ideia do nada. Os autores observaram estruturas recorrentes, deram nome a elas, documentaram sua intenção, descreveram seus participantes e consequências e ofereceram aos desenvolvedores um vocabulário para discutir projeto orientado a objetos.

O livro catalogou 23 padrões em três famílias: de criação, estruturais e comportamentais. Décadas depois, os detalhes do desenvolvimento de software mudaram drasticamente - computação em nuvem, microsserviços, contêineres, sistemas reativos, plataformas serverless, injeção de dependência, programação funcional e streaming de eventos são hoje comuns. Ainda assim, as perguntas centrais continuam reconhecíveis: como criamos objetos? Como compomos comportamento? Como isolamos a mudança? Como coordenamos a colaboração? É por isso que os padrões GoF continuam úteis.

Linha do tempo dos padrões arquiteturais de Christopher Alexander em 1977 até o livro da Gang of Four em 1994 e as arquiteturas modernas de nuvem e microsserviços
Figura 1: os padrões viajam da arquitetura para o software - o que muda é a tecnologia, não as forças de projeto recorrentes.

3. O que um padrão de projeto realmente é - e o que ele não é

3.1 Um padrão é um modelo de solução

Um padrão descreve um arranjo de responsabilidades que se mostrou útil repetidamente em determinado contexto. Strategy, por exemplo, diz que, quando vários algoritmos servem ao mesmo propósito e precisam variar independentemente do cliente, você pode encapsular esses algoritmos atrás de uma abstração comum e fazer o cliente depender dessa abstração. Ele não exige nomes de classe específicos nem um número específico de classes.

3.2 Um padrão não é código para copiar e colar

Copiar uma implementação de livro-texto sem entender as forças por trás dela é um dos caminhos mais rápidos para transformar padrões em complexidade acidental. A implementação deve se ajustar à linguagem, ao framework, à escala, à estratégia de testes e ao domínio. No Java moderno, um Strategy pode ser uma hierarquia de classes, uma interface funcional implementada com lambdas, uma coleção de beans do Spring ou um mapa de chaves de negócio para funções.

3.3 Um padrão tem consequências

Toda decisão de projeto introduz compromissos. Um padrão pode reduzir acoplamento enquanto aumenta a indireção. Pode melhorar a extensibilidade enquanto aumenta o número de tipos. Pode esclarecer responsabilidades enquanto torna o fluxo de controle menos evidente para um recém-chegado. Ler padrões com fluência exige, portanto, duas habilidades: reconhecer quando as forças justificam a estrutura e reconhecer quando a estrutura seria mais pesada que o problema.

3.4 Padrões como linguagem compartilhada

Talvez o maior valor de longo prazo do GoF seja a comunicação. “Use um Adapter na fronteira de pagamento” diz muito mais do que “crie outra classe”. O nome sugere intenção: traduzir uma interface externa em uma interna para que o domínio não fale a língua do fornecedor. O vocabulário compartilhado permite que discussões de arquitetura saiam da sintaxe e cheguem ao projeto.

4. As três famílias GoF

As três famílias GoF, a pergunta que cada uma responde e os padrões que contém.
FamíliaPergunta centralPadrões
CriaçãoComo objetos podem ser criados sem acoplar clientes a detalhes concretos de construção?Abstract Factory, Builder, Factory Method, Prototype, Singleton
EstruturaisComo classes e objetos podem ser compostos em estruturas flexíveis?Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy
ComportamentaisComo algoritmos, responsabilidades, comunicação e estado devem ser distribuídos?Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor

As categorias são úteis como mapa, mas projetos reais frequentemente combinam padrões. Uma aplicação pode usar Factory Method para escolher um Strategy, envolver o Strategy com um Decorator, expor uma operação simplificada por meio de um Facade, publicar resultados por eventos no estilo Observer e colocar um Adapter em volta de uma API externa. O valor não está em empilhar padrões; está em dar a cada problema recorrente uma estrutura apropriada.

Os 23 padrões GoF agrupados nas famílias de criação, estruturais e comportamentais com a pergunta que cada família responde
Figura 2: os 23 padrões organizados pela pergunta que cada família responde - cinco de criação, sete estruturais e onze comportamentais.

5. O catálogo completo dos 23 padrões GoF

O catálogo a seguir é intencionalmente prático. Para cada padrão, concentre-se na pressão de projeto que ele alivia, em vez de tentar memorizar um diagrama UML.

5.1 Abstract Factory (criação)

Criar famílias de objetos relacionados sem prender o cliente às suas classes concretas.

Aplicações típicas: Um kit de interface com famílias de componentes claros e escuros; uma abstração de nuvem que cria clientes de armazenamento, filas e banco de dados para AWS ou Azure.

Sinal de projeto

Use quando os produtos precisam variar juntos como uma família coerente. Evite quando apenas um produto varia ou quando introduzir um novo tipo de produto forçaria mudanças espalhadas nas fábricas.

5.2 Builder (criação)

Construir um objeto complexo passo a passo, tornando explícitos os parâmetros opcionais e as regras de construção.

Aplicações típicas: Requisições HTTP, relatórios, objetos de configuração, consultas, agregados de domínio, DTOs com muitos campos opcionais.

Sinal de projeto

Use quando construtores ficam ilegíveis, a construção tem etapas ou a validação pertence ao momento da construção. Não use apenas para deixar todo objeto simples fluente.

5.3 Factory Method (criação)

Definir uma operação de criação cujo produto concreto pode variar sem fazer o cliente depender diretamente de classes concretas.

Aplicações típicas: Serviços de notificação, parsers, exportadores, conectores, drivers, clientes de integração.

Sinal de projeto

Use quando decisões de criação devem ser extensíveis ou localizadas. Distinga-o do Simple Factory, que é útil, mas não é um dos 23 padrões formais do GoF.

5.4 Prototype (criação)

Criar novos objetos copiando um protótipo já configurado, em vez de reconstruí-los do zero.

Aplicações típicas: Objetos gráficos caros, modelos configurados, entidades de simulação, modelos de domínio pré-configurados.

Sinal de projeto

Útil quando a configuração é cara ou a clonagem é semanticamente natural. Tenha cuidado com cópias rasas versus profundas e com estado mutável compartilhado.

5.5 Singleton (criação)

Garantir que uma classe tenha uma única instância controlada e fornecer acesso a ela.

Aplicações típicas: Um coordenador ou recurso realmente único no processo, cuja unicidade faz parte do requisito.

Sinal de projeto

Use com cautela. O acesso global pode esconder dependências, prejudicar testes e criar estado global mutável. Contêineres de injeção de dependência muitas vezes eliminam a necessidade de Singletons manuais.

5.6 Adapter (estrutural)

Converter a interface de um componente na interface que outro componente espera.

Aplicações típicas: APIs legadas, SDKs de terceiros, provedores de pagamento, clientes de nuvem, migrações, integrações com fornecedores.

Sinal de projeto

Um dos padrões de fronteira mais fortes: impede que tipos externos, modelos de erro e convenções de nomenclatura vazem para o domínio.

5.7 Bridge (estrutural)

Separar uma abstração de sua implementação para que ambas as dimensões evoluam independentemente.

Aplicações típicas: Tipo de relatório x formato de saída, controle remoto x dispositivo, tipo de mensagem x transporte.

Sinal de projeto

Use quando duas dimensões independentes criariam uma explosão combinatória de subclasses.

5.8 Composite (estrutural)

Tratar objetos individuais e composições de maneira uniforme por meio de uma interface comum.

Aplicações típicas: Arquivos e pastas, menus e submenus, árvores organizacionais, árvores de expressão, hierarquias de componentes de interface.

Sinal de projeto

Mais adequado a estruturas em árvore em que os clientes devem executar operações sem se importar se um nó é uma folha ou um grupo.

5.9 Decorator (estrutural)

Acrescentar responsabilidades dinamicamente envolvendo um objeto que expõe a mesma abstração.

Aplicações típicas: Log, cache, compressão, criptografia, autorização, métricas, streams de I/O do Java.

Sinal de projeto

Use quando comportamentos precisam ser compostos em muitas combinações e a herança explodiria. Observe a ordem dos decoradores, porque a ordem de composição pode afetar a semântica.

5.10 Facade (estrutural)

Fornecer uma interface mais simples e de nível mais alto para um subsistema complexo.

Aplicações típicas: Orquestração de checkout, subsistemas de relatórios, invólucros de SDK, fluxos de onboarding.

Sinal de projeto

Use quando os chamadores não devem conhecer cada serviço interno. Um Facade simplifica o acesso; ele não exige esconder todo componente interno.

5.11 Flyweight (estrutural)

Compartilhar estado intrínseco entre muitos objetos logicamente separados para reduzir o uso de memória.

Aplicações típicas: Renderização de texto, objetos de jogo, editores gráficos, populações de objetos muito grandes com estado repetido.

Sinal de projeto

Valioso quando a pressão de memória é real e o estado repetido é substancial. Em sistemas de negócio comuns, é implementado explicitamente com menos frequência.

5.12 Proxy (estrutural)

Fornecer um substituto que controla o acesso a outro objeto mantendo uma interface compatível.

Aplicações típicas: Carregamento tardio, controle de acesso, cache, invocação remota, interceptação AOP, fronteiras transacionais.

Sinal de projeto

Diferentemente do Decorator, o Proxy controla primariamente acesso ou ciclo de vida; o comportamento adicionado é secundário a esse controle.

5.13 Chain of Responsibility (comportamental)

Passar uma requisição por uma sequência de manipuladores, cada um podendo processar, enriquecer, rejeitar ou encaminhar.

Aplicações típicas: Filtros HTTP, middleware, cadeias de validação, autorização, pipelines de eventos.

Sinal de projeto

Útil quando o processamento é naturalmente sequencial e os manipuladores devem ser combináveis de forma independente.

5.14 Command (comportamental)

Encapsular uma requisição ou ação como um objeto.

Aplicações típicas: Filas de tarefas, agendamento de jobs, ações de interface, trilhas de auditoria, novas tentativas, desfazer/refazer.

Sinal de projeto

Útil quando ações precisam ser armazenadas, adiadas, repetidas, registradas, compostas ou revertidas.

5.15 Interpreter (comportamental)

Representar uma gramática pequena e avaliar expressões nessa gramática.

Aplicações típicas: DSLs simples, filtros, expressões de regra, sintaxe de busca.

Sinal de projeto

Apropriado para gramáticas pequenas; linguagens grandes normalmente exigem geradores de parser ou ferramentas de análise especializadas.

5.16 Iterator (comportamental)

Percorrer uma coleção sem expor sua representação interna.

Aplicações típicas: Java Collections, estruturas de dados próprias, travessia de árvores.

Sinal de projeto

Tão profundamente embutido em Java que os desenvolvedores o usam constantemente por meio de Iterator e do for aprimorado.

5.17 Mediator (comportamental)

Centralizar a comunicação entre um conjunto de objetos que, de outro modo, dependeriam diretamente uns dos outros.

Aplicações típicas: Controles de interface, salas de bate-papo, coordenação de fluxos de trabalho, orquestração de módulos.

Sinal de projeto

Útil quando as dependências ponto a ponto formam um grafo denso. O próprio mediador não pode virar um God Object descontrolado.

5.18 Memento (comportamental)

Capturar e depois restaurar o estado de um objeto sem expor sua representação interna.

Aplicações típicas: Desfazer, snapshots de editor, checkpoints, restauração de estado.

Sinal de projeto

Útil quando é preciso um estado reversível e o encapsulamento deve permanecer intacto.

5.19 Observer (comportamental)

Definir uma dependência um-para-muitos para que as partes interessadas sejam notificadas quando algo muda.

Aplicações típicas: Eventos de domínio, listeners de interface, eventos em memória, notificações, atualizações reativas.

Sinal de projeto

Poderoso para desacoplar emissores de reações independentes. Em sistemas distribuídos, brokers de mensagens implementam ideias relacionadas de publicação/assinatura, mas acrescentam preocupações de entrega e consistência.

5.20 State (comportamental)

Permitir que um objeto mude de comportamento quando seu estado interno muda, normalmente delegando o comportamento específico a objetos de estado.

Aplicações típicas: Ciclo de vida de pedidos, fluxo de documentos, estado de conexão, processos de aprovação.

Sinal de projeto

Útil quando condicionais dirigidos por estado dominam uma classe e cada estado tem transições e comportamento distintos.

5.21 Strategy (comportamental)

Encapsular algoritmos intercambiáveis atrás de uma abstração comum.

Aplicações típicas: Frete, precificação, impostos, roteamento, autenticação, ordenação, regras de pagamento.

Sinal de projeto

Um padrão de trabalho pesado. Use quando alternativas resolvem a mesma tarefa e o cliente não deve ser dono da lógica condicional delas.

5.22 Template Method (comportamental)

Definir o esqueleto de um algoritmo em uma classe base, permitindo que subclasses personalizem etapas selecionadas.

Aplicações típicas: Pipelines de importação, pontos de extensão de frameworks, processamento em lote, fluxos de parsing.

Sinal de projeto

Útil quando a ordem do algoritmo é estável, mas algumas etapas variam. Prefira composição quando a herança criaria acoplamento rígido.

5.23 Visitor (comportamental)

Acrescentar operações a uma estrutura de objetos estável sem alterar repetidamente as classes dos elementos.

Aplicações típicas: ASTs de compiladores, árvores de documentos, análise estática, travessias de modelos complexos.

Sinal de projeto

Poderoso quando os tipos de elemento são estáveis e as operações mudam com frequência; desconfortável quando novos tipos de elemento são acrescentados com frequência.

6. Os padrões que você mais vai encontrar em Java

Não existe um ranking universal de frequência de padrões. O uso depende do domínio, da arquitetura, do framework e do estilo da equipe. Em Java corporativo e em sistemas de backend, porém, um grupo recorrente aparece explicitamente ou é incorporado pelos frameworks: Strategy, Factory Method, Builder, Adapter, Observer, Decorator, Facade, Singleton, Template Method e Command.

Os dez padrões mais encontrados em Java corporativo, o sinal que os revela e o ganho que trazem.
PadrãoSinal típico no códigoPor que ajuda
StrategyVários algoritmos servem ao mesmo propósito e uma cadeia de switch/if não para de crescer.Move a variação para trás de uma abstração estável e melhora o teste independente.
Factory MethodO código de negócio escolhe diretamente entre muitos construtores concretos.Separa as decisões de criação do uso.
BuilderConstrutores longos contêm muitos nulls, booleanos ou argumentos opcionais.Torna a construção legível e centraliza a validação.
AdapterO código de domínio importa tipos e regras de conversão específicos do fornecedor.Cria uma fronteira anticorrupção em torno de dependências externas.
ObserverUm evento provoca várias reações independentes.Permite novos listeners sem alterar o emissor.
DecoratorMuitas combinações de comportamento opcional criam explosão de subclasses.Compõe comportamento dinamicamente.
FacadeUm controlador ou cliente precisa coordenar serviços demais do subsistema.Cria um ponto de entrada simples para um caso de uso.
SingletonExatamente uma instância é um requisito real de domínio/execução.Controla a unicidade - mas não deve ser usado como conveniência de acesso global.
Template MethodVários fluxos compartilham a mesma sequência, mas diferem em algumas etapas.Centraliza o algoritmo invariante enquanto expõe pontos de extensão.
CommandAções precisam ser enfileiradas, registradas, agendadas, repetidas ou desfeitas.Transforma operações em objetos de primeira classe.

7. Aprofundamentos com exemplos em Java

As próximas seções seguem um modelo de aprendizado repetível: primeiro veja o problema de projeto, depois o padrão, depois o ganho no nível do código. Isso evita que os nomes dos padrões se desliguem das pressões que os tornam valiosos.

7.1 Padrão Strategy em Java - substitua algoritmos condicionais por comportamento intercambiável

Suponha que um sistema de e-commerce calcule o frete. A primeira versão suporta SEDEX, entrega padrão e retirada na loja. Um único método com ramificações if/else é compreensível. Mas então chegam novas transportadoras, entrega expressa, entrega no mesmo dia, frete internacional e frete promocional. A classe de cálculo se torna o lugar onde todas as regras de frete colidem.

public interface ShippingStrategy {
    double calculate(double weight);
}

public final class ExpressShipping implements ShippingStrategy {
    @Override
    public double calculate(double weight) {
        return weight * 15.0;
    }
}

public final class ShippingCalculator {
    private final ShippingStrategy strategy;

    public ShippingCalculator(ShippingStrategy strategy) {
        this.strategy = strategy;
    }

    public double calculate(double weight) {
        return strategy.calculate(weight);
    }
}

A calculadora agora é dona do fluxo “calcular frete”, enquanto cada Strategy é dono de um algoritmo. Uma nova política de frete pode ser acrescentada sem editar a calculadora. Cada política pode ser testada isoladamente, e a seleção em tempo de execução se torna direta.

Um switch crescente à esquerda substituído à direita por um cliente que depende de uma interface de estratégia de frete com três implementações independentes
Figura 3: o Strategy tira a variação do contexto - o switch para de crescer e cada algoritmo passa a ser testável de forma independente.

No Java 8+, o Strategy pode ser leve quando a abstração é uma interface funcional: ShippingStrategy free = weight -> 0.0;. A questão não é o número de classes; a questão é tirar a variação algorítmica de dentro do contexto.

Ganho no código

Menos condicionais, menor acoplamento, algoritmos isolados, testes mais simples e alinhamento mais forte com o Princípio Aberto/Fechado.

7.2 Factory Method em Java - separe a criação do uso

A lógica de criação vira um problema de projeto quando os clientes decidem repetidamente qual implementação concreta instanciar. Se o código de notificação está espalhado com new EmailNotifier(), new SmsNotifier() e new PushNotifier(), mudar as regras de construção exige tocar em código de negócio que deveria se importar apenas com enviar uma notificação.

public interface Notifier {
    void send(String message);
}

public abstract class NotificationService {
    protected abstract Notifier createNotifier();

    public void notify(String message) {
        Notifier notifier = createNotifier();
        notifier.send(message);
    }
}

public final class EmailNotificationService extends NotificationService {
    @Override
    protected Notifier createNotifier() {
        return new EmailNotifier();
    }
}

O Factory Method clássico coloca a operação de criação em uma abstração criadora e deixa que criadores concretos determinem o produto. Um método estático centralizado com um switch é comumente chamado de Simple Factory. Ele pode ser perfeitamente útil, mas não é um dos 23 padrões formais do GoF.

Ganho no código

A lógica de negócio depende de abstrações de produto, a criação fica localizada ou extensível, construtores concretos param de se espalhar pela base de código e os testes podem substituir caminhos de criação com mais facilidade.

7.3 Padrão Builder em Java - faça a construção complexa ser lida como uma história

Construtores longos são perigosos não porque construtores sejam ruins por natureza, mas porque argumentos posicionais deixam de comunicar significado. Considere new User("Ana", "ana@example.com", null, "London", true, false, null). Um leitor precisa inspecionar a assinatura do construtor para saber o que true e false significam, qual null é um telefone e quais campos são opcionais.

User user = User.builder()
    .name("Ana")
    .email("ana@example.com")
    .city("London")
    .active(true)
    .build();

Um Builder também pode impor invariantes no momento da construção. O método build() pode rejeitar um campo obrigatório ausente, normalizar dados ou escolher padrões. Isso transforma a construção em uma fronteira controlada, em vez de uma sequência passiva de atribuições.

Ganho no código

Construção legível, menos erros de ordem de parâmetros, suporte natural a valores opcionais, validação centralizada e menos pressão para criar múltiplos construtores telescópicos.

7.4 Padrão Adapter em Java - proteja o domínio de APIs externas

Uma biblioteca externa de pagamento pode aceitar valores em centavos, devolver códigos de status como texto, usar exceções específicas do fornecedor e expor terminologia que não pertence ao seu domínio. Se esses detalhes se espalham por serviços e controladores, o fornecedor reescreveu, na prática, a linguagem da sua aplicação.

public interface PaymentGateway {
    boolean pay(double amount);
}

public final class LegacyPaymentAdapter implements PaymentGateway {
    private final LegacyPaymentClient client;

    public LegacyPaymentAdapter(LegacyPaymentClient client) {
        this.client = client;
    }

    @Override
    public boolean pay(double amount) {
        long cents = Math.round(amount * 100);
        String response = client.executePayment(cents);
        return "OK".equals(response);
    }
}

O resto da aplicação fala PaymentGateway, não LegacyPaymentClient. Se o provedor for substituído, a lógica de conversão muda na fronteira, e não em todo lugar. Isso é mais do que compatibilidade de interface: é isolamento arquitetural.

Classes de domínio dependendo de uma interface PaymentGateway enquanto um adaptador traduz centavos, códigos de status em texto e exceções do fornecedor na fronteira
Figura 4: o Adapter é uma fronteira anticorrupção - os formatos do fornecedor param na borda e o domínio mantém a própria linguagem.

Ganho no código

Formatos e tipos externos ficam na borda, o domínio depende da própria abstração, trocar de fornecedor fica mais barato e os testes podem usar um PaymentGateway falso.

7.5 Padrão Observer em Java - desacople um evento de suas reações

Quando um pedido é pago, o sistema pode enviar um e-mail, atualizar o estoque, emitir uma nota fiscal, atualizar a analítica e notificar parceiros. Se o serviço de pagamento chama cada reação diretamente, ele conhece todos os consumidores e vira um centro de coordenação que precisa mudar sempre que uma nova reação é acrescentada.

public interface OrderPaidObserver {
    void onPaid(Order order);
}

public final class OrderService {
    private final List<OrderPaidObserver> observers = new ArrayList<>();

    public void addObserver(OrderPaidObserver observer) {
        observers.add(observer);
    }

    public void pay(Order order) {
        order.markAsPaid();
        observers.forEach(o -> o.onPaid(order));
    }
}

O padrão GoF em memória e a publicação/assinatura distribuída não são idênticos: brokers introduzem durabilidade, semântica de entrega, ordenação, novas tentativas e consistência. Ainda assim, a intuição de projeto é relacionada - o produtor não deveria precisar conhecer cada reação independente.

Ganho no código

Novos listeners podem ser introduzidos sem modificar o emissor, as responsabilidades ficam separadas e o projeto se torna um degrau natural rumo ao pensamento orientado a eventos.

7.6 Padrão Decorator em Java - componha comportamento opcional sem explosão de subclasses

Suponha que uma notificação possa ser registrada em log, criptografada, medida, repetida e auditada. Criar uma subclasse para cada combinação rapidamente produz EmailWithLog, EmailWithLogAndEncryption, SmsWithAuditAndMetrics e assim por diante. O Decorator usa composição no lugar disso: cada invólucro implementa a mesma abstração e delega para outra instância.

Notifier notifier =
    new LoggingNotifier(
        new EncryptingNotifier(
            new EmailNotifier()));

notifier.send("Order approved");

O I/O do Java é o exemplo clássico e familiar: um BufferedInputStream envolve outro InputStream e preserva a mesma abstração ampla. A mesma ideia aparece em middleware, filtros de segurança, clientes HTTP, observabilidade e preocupações transversais.

Ganho no código

O comportamento se torna combinável de forma independente, a explosão de subclasses é evitada e a ordem dos invólucros pode ser configurada em tempo de execução.

7.7 Padrão Facade em Java - transforme um subsistema em um ponto de entrada claro de caso de uso

Um endpoint de checkout não deveria precisar entender validação de estoque, cálculo de frete, captura de pagamento, emissão de nota, persistência e notificação do cliente. Quando um controlador coordena todos esses detalhes, o código de apresentação fica acoplado à topologia interna do subsistema.

public final class CheckoutFacade {
    public CheckoutResult checkout(Order order) {
        inventory.validate(order);
        shipping.calculate(order);
        payment.charge(order);
        invoice.issue(order);
        repository.save(order);
        return CheckoutResult.success(order.getId());
    }
}

// Controller
return checkoutFacade.checkout(order);

Um Facade cria uma interface conveniente de nível mais alto. Isso não significa que todo serviço interno precise se tornar privado ou inacessível. O objetivo é dar aos casos de uso comuns um ponto de entrada estável e impedir que os chamadores dependam de detalhes desnecessários.

Ganho no código

Controladores menores, orquestração centralizada, menor acoplamento entre cliente e subsistema e mais liberdade para evoluir componentes internos.

7.8 Padrão Singleton em Java - entenda o requisito antes da conveniência

O Singleton é famoso porque a implementação é fácil de reconhecer. Essa fama pode enganar. A pergunta importante não é “como escrevo getInstance()?”, e sim “a unicidade faz mesmo parte do modelo?”. Se a motivação real é simplesmente “quero acessar este objeto de qualquer lugar”, o resultado normalmente é acoplamento global escondido.

public enum GlobalConfiguration {
    INSTANCE;

    public void reload() {
        // ...
    }
}

Um enum pode fornecer um singleton robusto no nível da JVM em certos casos, mas a injeção de dependência moderna costuma oferecer um projeto melhor. Um bean do Spring pode ter escopo singleton e ainda assim ser injetado explicitamente nos consumidores. Isso torna as dependências visíveis nos construtores e mais fáceis de substituir em testes.

Ganho no código - somente quando justificado

Unicidade controlada e ciclo de vida consistente. Custo quando mal usado: estado global, dependências escondidas, atrito nos testes, acoplamento de inicialização e complexidade de concorrência.

7.9 Template Method em Java - mantenha o algoritmo estável e varie etapas selecionadas

Importadores de CSV e JSON podem compartilhar uma sequência geral: abrir, validar, ler, transformar, salvar, finalizar. Se cada importador reimplementa o fluxo inteiro, as partes estáveis são duplicadas. O Template Method coloca o esqueleto do algoritmo em uma classe base e delega etapas selecionadas às subclasses.

public abstract class Importer {
    public final void importData() {
        open();
        validate();
        Object data = read();
        Object transformed = transform(data);
        save(transformed);
        finish();
    }

    protected abstract void validate();
    protected abstract Object read();
    protected abstract Object transform(Object data);

    protected void open() {}
    protected void save(Object data) {}
    protected void finish() {}
}

O compromisso é a herança. Se as variações se tornam numerosas ou precisam se combinar de forma independente, a composição pode ser mais flexível. O Template Method é mais forte quando o próprio fluxo é estável e os pontos de extensão são intencionalmente limitados.

Ganho no código

A ordem comum é centralizada, a duplicação diminui e os pontos de extensão ficam explícitos.

7.10 Padrão Command em Java - transforme ações em objetos

Um botão, uma fila, um agendador ou um motor de fluxo de trabalho não deveria precisar conhecer os detalhes de implementação de cada ação que pode disparar. O Command envolve uma operação em um objeto que expõe um contrato comum de execução.

public interface Command {
    void execute();
}
public final class SaveDocumentCommand implements Command {
    private final DocumentFile document;

    public SaveDocumentCommand(DocumentFile document) {
        this.document = document;
    }

    @Override
    public void execute() {
        document.save();
    }
}

Queue<Command> jobs = new ArrayDeque<>();
jobs.add(new SaveDocumentCommand(document));

Uma vez que a ação é um objeto, ela pode ser armazenada, registrada, agendada, repetida, agrupada ou emparelhada com uma operação inversa para desfazer/refazer. Esse é o valor mais profundo do padrão: ele transforma uma operação de sintaxe de fluxo de controle em dado manipulável.

Ganho no código

O disparo fica desacoplado da execução, as ações podem ser enfileiradas ou auditadas e os fluxos ganham agendamento flexível e comportamento de nova tentativa.

8. Como reconhecer padrões em código existente

Reconhecer padrões é mais útil do que memorizá-los. O caminho mais rápido para aprender é mapear cheiros de código ou pressões de projeto para padrões candidatos e então perguntar se os compromissos se justificam.

Perguntas que revelam uma força de projeto e o padrão que normalmente vale considerar.
Pergunta a fazerPadrão candidato
Tenho vários algoritmos para a mesma tarefa?Strategy
O código de negócio está cheio de expressões new concretas usadas para selecionar implementações?Factory Method ou outro padrão de criação
Este construtor tem argumentos demais ou parâmetros opcionais demais?Builder
O domínio fala o vocabulário de uma API de terceiros?Adapter
Muitos componentes reagem de forma independente a um evento?Observer
Preciso de combinações de comportamento sem uma subclasse por combinação?Decorator
Um chamador precisa conhecer muitos serviços do subsistema para completar uma operação de negócio?Facade
As ramificações de if/switch dependem principalmente do estado atual?State
Uma requisição passa por múltiplos manipuladores opcionais?Chain of Responsibility
Preciso controlar acesso, ciclo de vida, preguiça ou invocação remota atrás da mesma interface?Proxy

Repare na linguagem das perguntas: vários algoritmos, vocabulário externo, muitos listeners, combinações de comportamento, estado atual. Essas são as forças. O nome do padrão é apenas um rótulo compacto para uma resposta comprovada a essas forças.

9. Padrões GoF e princípios SOLID

SOLID e GoF são relacionados, mas não equivalentes. SOLID fornece princípios que orientam a direção do projeto orientado a objetos. GoF fornece estruturas nomeadas que podem ajudar a realizar alguns desses princípios em situações recorrentes. Um padrão não deve ser justificado apenas dizendo “é SOLID”, e um projeto SOLID não exige um padrão GoF.

9.1 Strategy e o Princípio Aberto/Fechado

Um condicional grande muitas vezes significa que cada novo algoritmo modifica uma classe existente. Com Strategy, um novo algoritmo pode ser introduzido como uma nova implementação de uma abstração existente. O contexto pode permanecer fechado para modificação enquanto o conjunto de estratégias permanece aberto para extensão. Essa é uma forma prática do Princípio Aberto/Fechado.

9.2 Adapter e Inversão de Dependência

Quando o domínio depende de PaymentGateway em vez de VendorXClient, a política de alto nível é protegida de um detalhe de baixo nível. O Adapter traduz entre eles. Isso está bastante alinhado com o Princípio da Inversão de Dependência: a política estável depende de uma abstração que pertence à aplicação, não da interface concreta do fornecedor.

9.3 Facade e fronteiras de responsabilidade

Um Facade pode dar a um caso de uso uma fronteira clara de orquestração e evitar que controladores coordenem detalhes que não deveriam compreender. Ainda assim, um Facade enorme que conhece todos os subsistemas pode virar um God Object. Padrões não eliminam a necessidade de disciplina de responsabilidades.

9.4 Segregação de Interfaces e as interfaces dos padrões

Padrões costumam introduzir interfaces, mas “mais interfaces” não é automaticamente um projeto melhor. A interface deve expressar um papel significativo. Uma estratégia com uma operação focada costuma ser natural; uma interface que apenas espelha uma classe concreta enorme pode oferecer pouco desacoplamento. SOLID ajuda a avaliar a qualidade das abstrações em que os padrões se apoiam.

10. Onde os padrões aparecem no ecossistema Java

10.1 Spring

O Spring usa muitas ideias que são naturalmente discutidas com o vocabulário de padrões. Injeção de dependência e o contêiner IoC estão relacionados a criação de objetos e fábricas; os eventos de aplicação lembram Observer; AOP normalmente se apoia em proxies; interfaces no estilo strategy aparecem por todo o framework; e classes template historicamente centralizam fluxos estáveis enquanto expõem callbacks. As entranhas do framework nem sempre correspondem exatamente às estruturas GoF de livro-texto, mas o vocabulário torna a arquitetura mais fácil de raciocinar.

10.2 Hibernate e JPA

Entidades com carregamento tardio podem ser representadas por proxies. EntityManagerFactory claramente encarna um papel de criação. Frameworks de persistência também empregam padrões corporativos além do GoF - como Unit of Work e Identity Map -, demonstrando que o GoF é uma fundação, não o universo completo dos padrões de software.

10.3 I/O do Java

A hierarquia de InputStream é uma demonstração canônica de composição no estilo Decorator. Um BufferedInputStream envolve outro InputStream e acrescenta buffer sem alterar o contrato conceitual do cliente. Depois que você reconhece essa estrutura, a construção aninhada de streams deixa de parecer arbitrária.

10.4 Collections e Iterator

O Iterator é construído diretamente no ecossistema Java Collections. O for aprimorado esconde a mecânica, mas o princípio de projeto permanece: os clientes percorrem elementos sem depender da representação da coleção.

10.5 Injeção de dependência e a conversa moderna sobre Singleton

O escopo singleton gerenciado por framework não deve ser confundido com acesso estático global. Um contêiner de injeção de dependência pode gerenciar uma instância preservando relações explícitas de dependência. Essa distinção é central para usar escopos de ciclo de vida sem reproduzir os problemas de teste e acoplamento das implementações manuais de Singleton.

11. Comparações de padrões que evitam erros comuns

Pares de padrões frequentemente confundidos e a diferença que os separa.
PadrõesDiferença essencial
Strategy x StateAmbos delegam comportamento. Strategy representa um algoritmo escolhido; State representa comportamento que muda conforme o objeto percorre um ciclo de vida, muitas vezes com transições dirigidas por estado.
Decorator x ProxyAmbos envolvem um objeto compatível. O Decorator compõe primariamente responsabilidades; o Proxy controla primariamente acesso, ciclo de vida, preguiça, segurança ou remotização.
Adapter x FacadeO Adapter muda uma interface para que componentes possam cooperar. O Facade simplifica um subsistema oferecendo um ponto de entrada de nível mais alto.
Factory Method x Abstract FactoryO Factory Method foca em uma operação de criação que subclasses/criadores podem variar. O Abstract Factory cria famílias coordenadas de produtos relacionados.
Factory Method x Simple FactoryUm Simple Factory centraliza uma decisão de construção, muitas vezes com um switch. Útil, mas não é um dos 23 padrões formais do GoF.
Builder x FactoryUma Factory decide qual objeto/implementação criar. Um Builder foca em como um objeto complexo é montado passo a passo.
Bridge x StrategyO Bridge separa duas dimensões estruturais que variam independentemente. O Strategy troca algoritmos que cumprem o mesmo papel.
Template Method x StrategyO Template Method usa herança para variar etapas dentro de um algoritmo fixo. O Strategy usa composição para substituir um algoritmo ou política.
Observer x MediatorO Observer transmite mudanças a ouvintes interessados. O Mediator centraliza a colaboração entre pares para reduzir dependências cruzadas diretas.
Chain of Responsibility x DecoratorUma cadeia roteia uma requisição por manipuladores que podem continuar ou parar. Um decorador envolve um componente para acrescentar comportamento preservando sua interface.

12. Aplicação prática: refatorando um checkout de e-commerce

Um exemplo pequeno pode mostrar como os padrões se materializam juntos sem transformar o projeto em um museu de padrões. Considere um serviço de checkout que hoje executa seis responsabilidades em um único método: escolher um cálculo de frete com um switch, chamar diretamente o SDK de pagamento de um fornecedor, aplicar regras promocionais com outro switch, escrever mensagens de auditoria, emitir uma nota fiscal e notificar vários componentes a jusante.

12.1 Passo 1 - identifique os eixos de mudança

  • Os algoritmos de frete vão crescer de forma independente.
  • Os algoritmos de promoção mudam com frequência.
  • O provedor de pagamento pode ser substituído.
  • Auditoria e métricas são transversais e opcionais.
  • O endpoint de checkout deve expor uma operação de negócio.
  • Vários consumidores independentes reagem após o pagamento.

Cada afirmação descreve uma força. Somente depois de identificar essas forças os padrões devem entrar na conversa.

12.2 Passo 2 - mapeie forças para estruturas candidatas

Cada força do checkout e a estrutura que a responde.
ForçaCandidato
Algoritmos intercambiáveis de frete e promoçãoStrategy
SDK de pagamento externo com vocabulário incompatívelAdapter
Auditoria/métricas opcionais em torno de um gateway ou serviçoDecorator
Um ponto de entrada claro para o checkoutFacade
Reações independentes após o pagamento bem-sucedidoObserver / estilo evento de domínio
Selecionar uma estratégia concreta a partir da configuraçãoFactory Method ou uma pequena fábrica/registro
Método de checkout com seis responsabilidades misturadas à esquerda e o mesmo fluxo à direita dividido em strategy, adapter, decorator, facade e listeners
Figura 5: cada força do checkout ganha a própria estrutura - o fluxo continua o mesmo, mas cada eixo de mudança vai para a sua fronteira.

12.3 Passo 3 - mantenha pequenos os contratos voltados ao domínio

public interface ShippingPolicy {
    Money calculate(Order order);
}

public interface PaymentGateway {
    PaymentResult charge(Order order);
}

public interface OrderPaidListener {
    void onOrderPaid(Order order);
}

Os contratos expressam papéis de negócio. Eles não expõem centavos, textos de status do fornecedor, objetos de resposta HTTP ou detalhes específicos de framework. Esse é o retorno arquitetural do projeto: conceitos estáveis ficam dentro; detalhes voláteis ficam nas bordas.

12.4 Passo 4 - orquestre, não microgerencie

public CheckoutResult checkout(Order order) {
    Money shipping = shippingPolicy.calculate(order);
    order.applyShipping(shipping);

    PaymentResult paymentResult = paymentGateway.charge(order);
    if (!paymentResult.approved()) {
        return CheckoutResult.rejected(paymentResult.reason());
    }

    order.markAsPaid();
    repository.save(order);
    listeners.forEach(l -> l.onOrderPaid(order));
    return CheckoutResult.success(order.id());
}

Isto ainda é código comum. Os padrões não eliminaram condicionais, métodos ou dados. Eles moveram a variação e os detalhes de integração para fronteiras onde essas preocupações podem mudar de forma independente. Essa é a definição prática de projeto útil.

12.5 Passo 5 - teste as costuras

Como o checkout depende de abstrações pequenas, os testes de unidade podem injetar um ShippingPolicy falso e um PaymentGateway falso. Os testes de integração podem focar especificamente no Adapter. Os testes de listener podem verificar reações independentes. A arquitetura de testes resultante espelha a arquitetura de responsabilidades.

13. Superengenharia: quando não usar um padrão

Uma das lições mais importantes do manuscrito original é o alerta contra “usar um padrão por vaidade”. Uma função de duas linhas que dobra um número não precisa de DoubleStrategy, DoubleFactory, DoubleFacade, DoubleBuilder e cinco interfaces. O conhecimento de padrões deve reduzir a complexidade acidental, não gerá-la.

Vale considerar um padrão quando ele resolve um problema real e recorrente, reduz acoplamento nocivo, cria um ponto de extensão valioso, esclarece a intenção ou protege uma área estável de detalhes voláteis. Ele é suspeito quando apenas aumenta o número de classes, acrescenta indireção sem pressão de mudança, esconde uma lógica que já era óbvia ou é introduzido somente porque o padrão está na moda.

Regra prática

Prefira o projeto mais simples que permaneça claro diante das mudanças que você pode razoavelmente prever. Refatore rumo a um padrão quando a pressão ficar visível; não construa antecipadamente toda abstração possível para mudanças que talvez nunca aconteçam.

É por isso que um if pode ser a solução certa hoje e o Strategy a solução certa seis meses depois. O contexto decide. A qualidade de um projeto não se mede pelo número de padrões nomeados que ele contém.

14. Guia de decisão e matriz de seleção de padrões

Os 23 padrões com a família a que pertencem e a situação que torna cada um digno de consideração.
PadrãoFamíliaConsidere quando...
Abstract FactoryCriaçãoFamílias de produtos precisam variar juntas
BuilderCriaçãoA construção tem muitos parâmetros ou etapas ordenadas
Factory MethodCriaçãoA criação concreta precisa ser desacoplada ou extensível
PrototypeCriaçãoCopiar um modelo configurado é melhor do que reconstruir
SingletonCriaçãoA unicidade é genuinamente exigida
AdapterEstruturalA interface externa não combina com a interface da aplicação
BridgeEstruturalDuas dimensões variam independentemente
CompositeEstruturalObjetos folha e grupo formam uma árvore
DecoratorEstruturalComportamentos precisam ser combinados dinamicamente
FacadeEstruturalUm subsistema é complexo demais para os chamadores
FlyweightEstruturalPopulações enormes de objetos duplicam estado intrínseco
ProxyEstruturalAcesso/ciclo de vida precisa ser controlado atrás da mesma interface
Chain of ResponsibilityComportamentalUma requisição passa por manipuladores ordenados
CommandComportamentalUma ação precisa se tornar armazenável/manipulável
InterpreterComportamentalUma gramática pequena precisa ser avaliada
IteratorComportamentalA travessia deve esconder a representação da coleção
MediatorComportamentalPares têm dependências cruzadas demais
MementoComportamentalO estado precisa ser restaurado depois
ObserverComportamentalMuitos consumidores independentes reagem a um evento
StateComportamentalO comportamento depende fortemente do estado do ciclo de vida
StrategyComportamentalUm algoritmo precisa ser intercambiável
Template MethodComportamentalUm fluxo é estável, mas etapas selecionadas variam
VisitorComportamentalAs operações mudam com mais frequência que os tipos de elemento

14.1 Um mapa mental que começa pelo problema

Mapa de decisão ligando perguntas de projeto como trocar um algoritmo ou envolver a API de um fornecedor ao padrão que responde a cada uma
Figura 6: comece pela pergunta, não pelo catálogo - cada pressão de projeto aponta para a estrutura que a alivia.
Precisa trocar um algoritmo?                       -> Strategy
Precisa controlar/estender a criação?              -> Factory Method
Construção de objeto complexa?                     -> Builder
Interface de terceiro incompatível?                -> Adapter
Precisa de comportamento opcional combinável?      -> Decorator
Subsistema complexo demais para quem chama?        -> Facade
Muitos ouvintes reagem a um evento?                -> Observer
Ação precisa ser enfileirada/armazenada/repetida?  -> Command
Comportamento muda com o estado do ciclo de vida?  -> State
Requisição passa por etapas de processamento?    -> Chain of Responsibility
Duas dimensões variam independentemente?         -> Bridge
Árvore de folhas e grupos?                       -> Composite
Precisa de invólucro de acesso/preguiça/remoto?  -> Proxy

14.2 Autoavaliação do leitor

  • Você consegue explicar por que um padrão não é uma receita de código?
  • Você consegue listar os 5 padrões de criação, os 7 estruturais e os 11 comportamentais?
  • Você consegue distinguir Strategy de State?
  • Você consegue distinguir Decorator de Proxy?
  • Você consegue explicar por que o Simple Factory não é um dos 23 padrões formais do GoF?
  • Você consegue descrever como o Adapter protege o domínio das APIs de fornecedores?
  • Você consegue explicar por que o Singleton merece cautela em aplicações com injeção de dependência?
  • Você consegue identificar quando um if/switch ainda é mais simples e melhor do que introduzir um padrão?

15. Conclusão

Os 23 padrões de projeto GoF são mais valiosos quando deixam de parecer 23 receitas e passam a parecer 23 lentes para examinar problemas de projeto recorrentes. Os padrões de criação perguntam como objetos podem ser criados sem prender o sistema desnecessariamente a detalhes concretos de construção. Os padrões estruturais perguntam como objetos e classes podem ser combinados sem tornar o projeto rígido. Os padrões comportamentais perguntam como algoritmos, estado, comunicação e responsabilidade devem ser distribuídos.

No Java do dia a dia, Strategy, Factory Method, Builder, Adapter, Observer, Decorator, Facade, Singleton, Template Method e Command são especialmente úteis de reconhecer. Mas o objetivo nunca é “ter mais interfaces” ou “usar um padrão famoso”. O Strategy é valioso porque algoritmos podem mudar sem desmontar seu contexto. O Adapter é valioso porque dependências externas param de ditar a linguagem do domínio. O Builder é valioso porque a construção complexa se torna legível e segura. O Facade é valioso porque um subsistema complexo pode expor um ponto de entrada de negócio claro. O Command é valioso porque ações viram objetos que podem ser agendados, repetidos, registrados ou desfeitos.

A lição mais profunda é o discernimento. Um bom engenheiro não começa com “qual padrão posso usar?”. O engenheiro começa com “o que está mudando? O que está acoplado? O que está repetido? O que deveria permanecer estável? Qual é o projeto mais simples capaz de absorver a mudança provável?”. Às vezes a resposta é um padrão. Às vezes a resposta é um método direto e um if claro.

Minha avaliação é que o GoF continua sendo uma das fundações mais úteis para a educação em projeto de software justamente porque ensina um vocabulário transferível. Frameworks modernos, construções funcionais, injeção de dependência, sistemas de eventos e arquiteturas de nuvem podem mudar a forma da implementação, mas as forças fundamentais de projeto permanecem. Quando você consegue olhar para um condicional crescente e perguntar “Strategy ou State?”, olhar para um SDK de fornecedor vazando para o domínio e perguntar “Adapter?”, ou olhar para um controlador coordenando oito serviços e perguntar “Facade?”, o assunto saiu da memorização e entrou no projeto de software.

Pensamento final

A melhor evidência de que você entende padrões de projeto não é conseguir nomear todos os 23. É conseguir explicar por que um padrão torna determinada mudança mais barata - e por que, em outro contexto, nenhum padrão é o melhor projeto.

Referências e leitura complementar

  1. Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994.
  2. Alexander, Christopher; Ishikawa, Sara; Silverstein, Murray et al. A Pattern Language: Towns, Buildings, Construction. Oxford University Press, 1977.
  3. Fowler, Martin. Refactoring: Improving the Design of Existing Code. Addison-Wesley.
  4. Martin, Robert C. Clean Architecture e textos sobre os princípios SOLID.
  5. O manuscrito GoF/Java fornecido, usado como base editorial principal deste artigo ampliado.