Disclaimer
Este texto foi inicialmente concebido pela IA Generativa em função da transcrição de um vídeo do canal Dev + Eficiente. Se preferir acompanhar por vídeo, é só dar o play.
Introdução
Quando falamos de design patterns, a maioria dos desenvolvedores logo pensa no famoso livro do Gang of Four e nos exemplos clássicos que aparecem em tutoriais. Mas existe uma distância enorme entre conhecer a teoria dos padrões e ser capaz de identificá-los e aplicá-los em códigos reais. Neste post, vamos explorar como diversos design patterns são aplicados em projetos open source que você provavelmente já usa, como Spring Boot, Hibernate e a própria API do Java.
A efetividade do aprendizado está diretamente conectada com a especificidade do treino. Se você só vê exemplos artificiais, criados especificamente para demonstrar um pattern, você aprende sobre o padrão, mas não treina para aplicá-lo no dia a dia. O código real não grita "aplique um Factory Method aqui!" - você precisa desenvolver a habilidade de perceber oportunidades e aplicar as técnicas adequadas.
A Base dos Design Patterns: Mais do que Decorar Nomes
Antes de mergulhar nos exemplos práticos, é fundamental entender que design patterns têm objetivos claros e técnicas específicas para alcançá-los.
As Ideias Principais
Todos os design patterns convergem para dois objetivos fundamentais:
- Promover extensibilidade: permitir que o código evolua sem quebrar o que já existe
- Promover organização de código: estruturar o software de forma mais compreensível e manutenível
Como Chegar Lá
Para atingir esses objetivos, os patterns geralmente utilizam três técnicas principais:
- Encapsular comportamentos: isolar funcionalidades que precisam ser reutilizáveis
- Dynamic dispatch: usar polimorfismo (em linguagens orientadas a objetos) ou passar funções como parâmetros (em linguagens funcionais)
- Composição: criar estruturas que são compostas por outras estruturas
O Poder da Nomenclatura
Dar nomes aos padrões facilita tremendamente a comunicação. Em vez de explicar "eu criei um objeto cuja criação envolvia diversos passos internos e isolei isso num método", você simplesmente diz "criei um factory method". Essa nomenclatura compartilhada acelera a comunicação entre desenvolvedores.
Design Patterns em Ação: Exemplos do Mundo Real
Factory Method: Spring Boot ApplicationContext
O Spring Boot oferece um exemplo perfeito do Factory Method em ação. Quando você inicia uma aplicação Spring, utiliza algo como:
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(MinhaClasse.class, args);
ProviderDefinitionRepository provider = context.getBean(ProviderDefinitionRepository.class);
}
O método getBean()
funciona como um factory method. Ele encapsula toda a complexidade de criar objetos dinamicamente, utilizando metaprogramação e gerenciamento de dependências, mas você não precisa saber exatamente como isso acontece internamente.
A interface BeanFactory
define os comportamentos que qualquer fábrica de beans deve implementar, permitindo diferentes implementações como ClassPathXmlApplicationContext
, que carrega beans a partir de configurações XML.
Builder: StringBuilder e Lombok
O padrão Builder aparece claramente na classe StringBuilder
do Java:
StringBuilder textao = new StringBuilder();
textao.append("Primeiro parágrafo")
.append("Segundo parágrafo")
.append("Terceiro parágrafo");
String resultado = textao.toString();
Em vez de concatenar strings diretamente (o que seria ineficiente), o StringBuilder permite construir o objeto passo a passo, facilitando a criação de textos complexos.
O projeto Lombok leva essa ideia adiante, gerando builders automaticamente através de anotações. Com @Builder
, você pode transformar uma classe com múltiplos atributos em uma API fluente de construção, tornando o código mais legível e menos propenso a erros.
Adapter: Arrays.asList()
O método Arrays.asList()
é um exemplo perfeito do padrão Adapter. Você tem um array (estrutura de dados com acesso direto por índice) e precisa operá-lo como uma lista (interface com métodos como size()
, get()
, etc.).
Internamente, o método cria uma implementação de lista que compõe com o array original. Quando você chama size()
, por baixo dos panos ele chama length
do array. Quando você chama get(index)
, ele faz acesso direto ao array. O adapter permite que dois tipos incompatíveis trabalhem juntos sem modificar nenhum dos dois.
Proxy: Hibernate Lazy Loading
O Hibernate demonstra o padrão Proxy de forma elegante no lazy loading de relacionamentos. Considere uma entidade Produto
com uma coleção de Ofertas
:
Produto produto = entityManager.find(Produto.class, 1L);
Set<Oferta> ofertas = produto.getOfertas(); // Primeira chamada: dispara query
Set<Oferta> ofertas2 = produto.getOfertas(); // Segunda chamada: usa cache
O Hibernate cria uma implementação proxy da interface Set
que tem inteligência para decidir quando ir ao banco de dados e quando usar dados em cache. Na primeira chamada, executa a query; nas chamadas subsequentes, retorna os dados já carregados.
Strategy: Spring Security Authentication
O Spring Security utiliza o padrão Strategy através da interface AuthenticationProvider
. Diferentes implementações podem lidar com diferentes tipos de autenticação:
-
RememberMeAuthenticationProvider
para autenticação via "lembrar-me" -
DaoAuthenticationProvider
para autenticação via banco de dados - Implementações customizadas que você pode criar
Todas expõem a mesma interface pública (authenticate(Authentication)
), mas cada uma implementa uma estratégia diferente de autenticação. Isso permite trocar mecanismos de autenticação sem afetar o restante do sistema.
Template Method: HttpServlet
A especificação de Servlets do Java demonstra o Template Method através da classe abstrata HttpServlet
. Ela define um fluxo padrão no método service()
:
protected void service(HttpServletRequest req, HttpServletResponse resp) {
String method = req.getMethod();
if (method.equals("GET")) {
doGet(req, resp);
} else if (method.equals("POST")) {
doPost(req, resp);
}
// ... outros métodos HTTP
}
O fluxo principal está definido, mas os métodos específicos (doGet()
, doPost()
, etc.) são deixados para as classes filhas implementarem conforme necessário. Você herda de HttpServlet
e implementa apenas os métodos HTTP que sua aplicação precisa suportar.
Reconhecendo Padrões no Código Real
O grande desafio não é decorar as implementações dos patterns, mas desenvolver a habilidade de reconhecer quando aplicá-los. Aqui estão alguns sinais que podem indicar oportunidades:
- Código repetitivo: pode se beneficiar de um Template Method
- Múltiplos ifs para escolher implementações: candidate a Strategy
- Criação complexa de objetos: oportunidade para Factory Method
- Múltiplos passos para criação de um objeto: oportunidade para Builder
- Interfaces incompatíveis: situação ideal para Adapter
- Necessidade de adicionar comportamento sem modificar código existente: considere Proxy
Técnicas Fundamentais em Ação
Observe como todos os exemplos utilizam as técnicas fundamentais mencionadas:
- Encapsulamento: cada pattern isola responsabilidades específicas
- Polimorfismo: as diferentes implementações são intercambiáveis através de interfaces comuns
- Composição: os proxies compõem com implementações reais, os adapters compõem com tipos incompatíveis
Conclusão
A verdadeira habilidade com design patterns não está em decorar implementações, mas em reconhecer oportunidades de aplicação e escolher as técnicas adequadas. Os patterns são ferramentas para atingir extensibilidade e organização de código, não fins em si mesmos.
Quando você entende as técnicas fundamentais (encapsulamento, polimorfismo, composição) e os objetivos (extensibilidade e organização), você pode aplicar esses conceitos mesmo sem lembrar do nome específico de cada pattern. A nomenclatura é importante para comunicação, mas a essência está na capacidade de identificar um problema e escolher o caminho adequado para resolver.
Para desenvolver essa habilidade, procure códigos da vida real - projetos open source, bibliotecas que você usa, frameworks populares. Analise como eles resolvem problemas complexos e tente identificar os padrões aplicados. Essa prática específica vai treinar seu olho para reconhecer oportunidades no seu próprio código.
Dev+ Eficiente
Se você gostou deste conteúdo, conheça a Jornada Dev + Eficiente, nosso treinamento focado em fazer com que você se torne uma pessoa cada vez mais capaz de entregar software que gera valor na ponta final, com máximo de qualidade e eficiência.
Acesse https://deveficiente.com/oferta-20-por-cento para conhecer tudo que oferecemos.
Top comments (0)