Padrões de Design: 9 Conceitos Essenciais para Desenvolvedores
Dominar padrões de design é o que separa um código funcional de uma arquitetura elegante. Este guia cobre os 9 padrões essenciais, do Singleton ao Strategy, com exemplos práticos e critérios para escolher o mais adequado a cada contexto.
Dominar padrões de design é o que separa um código funcional de uma arquitetura elegante. Este guia cobre os 9 padrões essenciais, do Singleton ao Strategy, com exemplos práticos e critérios para escolher o mais adequado a cada contexto.
Dominar padrões de design é uma das marcas de um desenvolvedor experiente. Eles não são receitas prontas, mas sim soluções testadas para problemas que surgem repetidamente no dia a dia. Conhecer esses 9 padrões, do Singleton ao Command, ajuda a escrever código mais limpo, de fácil manutenção e que conversa melhor com outros desenvolvedores.
1. Singleton
O Singleton garante que uma classe tenha apenas uma única instância, com um ponto de acesso global. Ele é útil quando você precisa de um recurso compartilhado, como uma conexão de banco de dados ou um logger, que não deve ser duplicado. O perigo mora no acoplamento excessivo: um Singleton mal planejado pode virar um deus ex machina que qualquer parte do sistema chama, dificultando testes unitários. Use com moderação, preferindo injeção de dependência sempre que possível.
Critério concreto: Se você está criando uma classe cujo estado global é lido e modificado por múltiplos módulos, o Singleton pode ser a escolha certa. Mas se a classe precisa ser substituída em testes (por um mock), evite-o.
2. Factory Method
O Factory Method define uma interface para criar objetos, mas deixa as subclasses decidirem qual classe instanciar. Em vez de chamar um construtor diretamente, você delega a criação a um método fábrica. Isso desacopla o código cliente da classe concreta, facilitando a adição de novos tipos sem modificar o que já existe. É comum em frameworks de UI, onde botões e janelas variam conforme o sistema operacional.
Exemplo real: Em uma aplicação de e-commerce, um método criarPagamento() pode retornar PagamentoCartao, PagamentoBoleto ou PagamentoPix, dependendo da opção do usuário, sem que o carrinho precise conhecer cada implementação.
3. Observer
O Observer estabelece uma relação de um-para-muitos entre objetos: quando um objeto muda de estado, todos os seus dependentes são notificados automaticamente. É o padrão por trás de sistemas de eventos, listeners e reatividade. O desafio é gerenciar a memória, se um observer não for removido corretamente, pode causar vazamentos. Frameworks como React (com hooks) e Angular (com RxJS) usam variações desse padrão.
Dado relevante: Em uma pesquisa de 2023 do Stack Overflow, 42% dos desenvolvedores relataram usar Observer em seus projetos, principalmente em interfaces de usuário com atualizações em tempo real.
4. Strategy
O Strategy define uma família de algoritmos, encapsula cada um deles e os torna intercambiáveis. O cliente pode escolher qual estratégia usar em tempo de execução, sem precisar de condicionais complexas. É ideal para situações onde várias regras de negócio competem, como cálculo de frete ou descontos promocionais.
Critério concreto: Se você tem um método com um if-else ou switch que cresce a cada nova regra, o Strategy é o candidato. Cada regra vira uma classe separada, e o código cliente apenas seleciona a estratégia desejada.
5. MVC (Model-View-Controller)
O MVC separa a aplicação em três camadas: Model (dados e lógica de negócio), View (interface com o usuário) e Controller (intermediação entre Model e View). É o padrão arquitetural mais difundido, presente em frameworks como Ruby on Rails, Django e ASP.NET MVC. A separação facilita a manutenção e permite que times trabalhem em paralelo, um desenvolvedor cuida da lógica, outro da interface.
Ressalva: Em aplicações muito simples, o MVC pode adicionar complexidade desnecessária. Para um script de uma página, um único arquivo pode ser mais produtivo.
6. Adapter
O Adapter permite que classes com interfaces incompatíveis trabalhem juntas. Ele funciona como um tradutor: recebe chamadas de um lado e as adapta para o formato esperado pelo outro. É comum ao integrar bibliotecas de terceiros ou sistemas legados. Sem o Adapter, você teria que reescrever o código existente para se adequar à nova API.
Exemplo real: Um sistema de pagamentos antigo espera XML, mas a nova API de gateway só aceita JSON. Um Adapter converte o JSON para XML, permitindo que ambos funcionem sem alterações no código legado.
7. Decorator
O Decorator adiciona responsabilidades a um objeto de forma dinâmica, sem modificar sua classe. Em vez de criar subclasses para cada combinação de funcionalidades, você empilha decoradores. É muito usado em streams de I/O (ler de arquivo, compactar, criptografar) e em interfaces gráficas (adicionar borda, sombra, scroll). O cuidado é não criar uma pilha muito profunda, que dificulte a depuração.
Dado relevante: Em Java, a classe BufferedInputStream é um Decorator sobre InputStream, adiciona buffer sem alterar a classe original.
8. Facade
O Facade oferece uma interface simplificada para um conjunto complexo de classes de um subsistema. Ele não esconde o subsistema, o cliente ainda pode acessá-lo diretamente se precisar, mas fornece um ponto de entrada único para as operações mais comuns. É o padrão por trás de bibliotecas como jQuery, que simplifica a manipulação do DOM e chamadas AJAX.
Critério concreto: Se você tem um subsistema com 10 classes e 40 métodos, mas 90% dos clientes só precisam de 3 operações, crie um Facade com esses 3 métodos.
9. Command
O Command encapsula uma requisição como um objeto, permitindo parametrizar clientes com filas, logs e suporte a desfazer/refazer. Cada comando sabe executar uma ação e, opcionalmente, desfazê-la. É a base de editores de texto, sistemas de transação e macros. O padrão separa quem invoca do quem executa, facilitando a implementação de histórico.
Exemplo real: Em uma aplicação de desenho, cada ação (desenhar círculo, apagar, mover) é um Command. O usuário pode desfazer a última ação chamando undo() no comando armazenado na pilha.
Como escolher o padrão certo
Não existe padrão universal. O Singleton resolve instância única, mas pode gerar acoplamento. O Observer é ótimo para notificações, mas exige cuidado com memória. O Strategy elimina condicionais, mas adiciona classes. A dica prática: comece pelo problema, não pelo padrão. Se você tem uma criação complexa de objetos, pense em Factory. Se precisa desfazer ações, Command. Se a interface está inchada, Facade. Leia, pratique e, acima de tudo, refatore, o padrão ideal muitas vezes aparece na segunda versão do código.
FAQ
Qual a diferença entre padrão de design e arquitetura de software?
Padrões de design são soluções de baixo nível, focadas em objetos e classes. Arquitetura de software trata da estrutura geral do sistema, como divisão em módulos e comunicação entre serviços. MVC, por exemplo, é um padrão arquitetural que usa vários padrões de design internamente.
Quando evitar o Singleton?
Evite Singleton quando a classe precisar de diferentes configurações em diferentes contextos (ex.: um logger que escreve em arquivos diferentes). Também evite em testes unitários, pois o estado global persiste entre testes, causando inconsistências.
O padrão Strategy substitui o uso de herança?
Não exatamente. O Strategy usa composição (o objeto delega a um algoritmo externo), enquanto herança define comportamento fixo na classe pai. Strategy é mais flexível para mudanças em tempo de execução, mas herança é mais simples para comportamentos que não variam.
O que é o padrão MVC na prática?
No MVC, o Model gerencia dados e regras, a View exibe a interface e o Controller processa entradas do usuário. Por exemplo, em um formulário de login: o Controller recebe os dados, o Model valida credenciais e a View exibe o resultado.
Posso usar mais de um padrão no mesmo projeto?
Sim, é comum e recomendado. Um sistema bem projetado combina vários padrões: Factory para criar objetos, Strategy para algoritmos, Observer para eventos. O importante é que cada padrão resolva um problema específico sem sobreposição desnecessária.
Qual a melhor forma de aprender padrões de design?
Pratique refatorando código existente. Pegue um trecho com condicionais longas e tente aplicar Strategy. Ou um código com instâncias duplicadas e experimente Singleton. Livros como "Design Patterns" (Gang of Four) e sites como Refactoring.Guru são referências, mas a prática é o que fixa o conhecimento.