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.
Por João Ricardo Dutra••Material completo
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
Introdução: por que o projeto importa mais do que código que funciona
Da arquitetura ao software: a história por trás dos padrões de projeto
O que um padrão de projeto realmente é - e o que ele não é
As três famílias GoF
O catálogo completo dos 23 padrões GoF
Os padrões que você mais vai encontrar em Java
Aprofundamentos com exemplos em Java
Como reconhecer padrões em código existente
Padrões GoF e princípios SOLID
Onde os padrões aparecem no ecossistema Java
Comparações de padrões que evitam erros comuns
Aplicação prática: refatorando um checkout de e-commerce
Superengenharia: quando não usar um padrão
Guia de decisão e matriz de seleção de padrões
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.
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ília
Pergunta central
Padrões
Criação
Como objetos podem ser criados sem acoplar clientes a detalhes concretos de construção?
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.
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.
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.
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ão
Sinal típico no código
Por que ajuda
Strategy
Vá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 Method
O código de negócio escolhe diretamente entre muitos construtores concretos.
Separa as decisões de criação do uso.
Builder
Construtores longos contêm muitos nulls, booleanos ou argumentos opcionais.
Torna a construção legível e centraliza a validação.
Adapter
O 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.
Observer
Um evento provoca várias reações independentes.
Permite novos listeners sem alterar o emissor.
Decorator
Muitas combinações de comportamento opcional criam explosão de subclasses.
Compõe comportamento dinamicamente.
Facade
Um controlador ou cliente precisa coordenar serviços demais do subsistema.
Cria um ponto de entrada simples para um caso de uso.
Singleton
Exatamente 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 Method
Vários fluxos compartilham a mesma sequência, mas diferem em algumas etapas.
Centraliza o algoritmo invariante enquanto expõe pontos de extensão.
Command
Açõ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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
publicabstractclassImporter{publicfinalvoidimportData(){open();validate();Object data =read();Object transformed =transform(data);save(transformed);finish();}protectedabstractvoidvalidate();protectedabstractObjectread();protectedabstractObjecttransform(Object data);protectedvoidopen(){}protectedvoidsave(Object data){}protectedvoidfinish(){}}
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.
publicinterfaceCommand{voidexecute();}
publicfinalclassSaveDocumentCommandimplementsCommand{privatefinalDocumentFile document;publicSaveDocumentCommand(DocumentFile document){this.document = document;}@Overridepublicvoidexecute(){
document.save();}}Queue<Command> jobs =newArrayDeque<>();
jobs.add(newSaveDocumentCommand(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 fazer
Padrã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ões
Diferença essencial
Strategy x State
Ambos 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 Proxy
Ambos 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 Facade
O 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 Factory
O 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 Factory
Um 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 Factory
Uma Factory decide qual objeto/implementação criar. Um Builder foca em como um objeto complexo é montado passo a passo.
Bridge x Strategy
O Bridge separa duas dimensões estruturais que variam independentemente. O Strategy troca algoritmos que cumprem o mesmo papel.
Template Method x Strategy
O 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 Mediator
O 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 Decorator
Uma 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ça
Candidato
Algoritmos intercambiáveis de frete e promoção
Strategy
SDK de pagamento externo com vocabulário incompatível
Adapter
Auditoria/métricas opcionais em torno de um gateway ou serviço
Decorator
Um ponto de entrada claro para o checkout
Facade
Reações independentes após o pagamento bem-sucedido
Observer / estilo evento de domínio
Selecionar uma estratégia concreta a partir da configuração
Factory Method ou uma pequena fábrica/registro
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
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.
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ão
Família
Considere quando...
Abstract Factory
Criação
Famílias de produtos precisam variar juntas
Builder
Criação
A construção tem muitos parâmetros ou etapas ordenadas
Factory Method
Criação
A criação concreta precisa ser desacoplada ou extensível
Prototype
Criação
Copiar um modelo configurado é melhor do que reconstruir
Singleton
Criação
A unicidade é genuinamente exigida
Adapter
Estrutural
A interface externa não combina com a interface da aplicação
Bridge
Estrutural
Duas dimensões variam independentemente
Composite
Estrutural
Objetos folha e grupo formam uma árvore
Decorator
Estrutural
Comportamentos precisam ser combinados dinamicamente
Facade
Estrutural
Um subsistema é complexo demais para os chamadores
Flyweight
Estrutural
Populações enormes de objetos duplicam estado intrínseco
Proxy
Estrutural
Acesso/ciclo de vida precisa ser controlado atrás da mesma interface
Chain of Responsibility
Comportamental
Uma requisição passa por manipuladores ordenados
Command
Comportamental
Uma ação precisa se tornar armazenável/manipulável
Interpreter
Comportamental
Uma gramática pequena precisa ser avaliada
Iterator
Comportamental
A travessia deve esconder a representação da coleção
Mediator
Comportamental
Pares têm dependências cruzadas demais
Memento
Comportamental
O estado precisa ser restaurado depois
Observer
Comportamental
Muitos consumidores independentes reagem a um evento
State
Comportamental
O comportamento depende fortemente do estado do ciclo de vida
Strategy
Comportamental
Um algoritmo precisa ser intercambiável
Template Method
Comportamental
Um fluxo é estável, mas etapas selecionadas variam
Visitor
Comportamental
As operações mudam com mais frequência que os tipos de elemento
14.1 Um mapa mental que começa pelo problema
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
Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994.
Alexander, Christopher; Ishikawa, Sara; Silverstein, Murray et al. A Pattern Language: Towns, Buildings, Construction. Oxford University Press, 1977.
Fowler, Martin. Refactoring: Improving the Design of Existing Code. Addison-Wesley.
Martin, Robert C. Clean Architecture e textos sobre os princípios SOLID.
O manuscrito GoF/Java fornecido, usado como base editorial principal deste artigo ampliado.