Pular para o conteúdo
10 min de leitura

Design patterns essenciais: os padrões que você vai reconhecer no código de todo dia

Por Equipe Tech do Sonne ·

Design patterns não são receitas para decorar — são vocabulário compartilhado. Um guia dos padrões que aparecem de verdade no código do dia a dia, e do risco de exagerar.

Neste artigo

Poucos assuntos em programação geram tanto mal-entendido quanto os design patterns. De um lado, há quem os trate como um catálogo sagrado a ser aplicado sempre que possível, enchendo o código de fábricas, estratégias e decoradores onde um if resolveria. De outro, há quem os despreze como jargão acadêmico dos anos noventa, sem perceber que os usa o tempo todo sem saber o nome. As duas posições erram pelo mesmo motivo: confundem o que um padrão realmente é.

Um design pattern não é um pedaço de código para copiar nem uma regra para seguir. É a descrição de uma solução recorrente para um problema recorrente de projeto — e, principalmente, é um nome. Quando você diz a um colega "aqui eu usei uma Strategy", comunica em duas palavras uma estrutura inteira que levaria um parágrafo para explicar. Esse é o valor central dos padrões: eles são vocabulário compartilhado, uma linguagem que permite discutir arquitetura sem desenhar tudo de novo a cada conversa. Este artigo apresenta um punhado dos padrões que aparecem de verdade no código profissional, explicando o problema que cada um resolve, e termina com o alerta necessário sobre o exagero — porque padrão demais é um problema tão real quanto padrão de menos.

Padrão é vocabulário, não receita#

A ideia de catalogar soluções recorrentes ganhou força com o livro que quase todo mundo chama de "o GoF" — Design Patterns, escrito por quatro autores (a "Gangue dos Quatro") em 1994. Eles não inventaram os padrões; observaram que bons projetistas resolviam certos problemas repetidamente das mesmas formas, e deram nome a esses formatos para que pudessem ser ensinados e discutidos.

O benefício está na comunicação, não na cópia. Saber que existe um padrão chamado Observer não te obriga a usá-lo; te dá uma palavra para reconhecer aquela estrutura quando ela aparece e para propô-la quando ela cabe. Em uma revisão de código, "isso está pedindo um Adapter" transmite instantaneamente uma intenção que, sem o termo, exigiria explicar o encaixe entre duas interfaces incompatíveis do zero.

Padrões são independentes de linguagem, mas não de contexto. Muitos padrões clássicos nasceram para suprir limitações de linguagens orientadas a objetos rígidas. Em linguagens com funções de primeira classe, por exemplo, uma Strategy inteira pode virar simplesmente passar uma função como argumento. Conhecer o padrão ajuda a reconhecer o problema; a solução idiomática depende das ferramentas que a sua linguagem oferece. Aplicar a estrutura verbosa do GoF onde a linguagem já resolve de forma nativa é confundir o mapa com o território.

As três famílias#

O catálogo clássico organiza os padrões em três grupos, e conhecer as categorias já ajuda a saber onde procurar quando um problema aparece.

Criacionais cuidam de como os objetos nascem. Eles isolam e flexibilizam o processo de criação — quando como um objeto é construído importa e você quer desacoplar o resto do código desse detalhe. Factory e Builder vivem aqui.

Estruturais cuidam de como os objetos se compõem. Eles lidam com o encaixe entre partes: adaptar interfaces incompatíveis, adicionar comportamento sem alterar a classe original, compor objetos em estruturas maiores. Adapter e Decorator são exemplos.

Comportamentais cuidam de como os objetos interagem. Eles descrevem a comunicação e a divisão de responsabilidades entre objetos — quem chama quem, como o comportamento varia, como a notificação se espalha. Strategy e Observer pertencem a este grupo.

Os padrões que aparecem de verdade#

Não vale decorar os vinte e três do GoF. Vale entender bem os poucos que você vai encontrar toda semana, seja lendo código dos outros, seja escrevendo o seu.

Factory: criar sem amarrar ao tipo concreto#

O problema que a Factory resolve é a proliferação de new espalhado pelo código. Quando várias partes instanciam diretamente uma classe concreta, trocar essa classe por outra vira uma caçada. Uma factory centraliza a criação atrás de uma função ou método, de modo que o resto do código pede "me dê um processador de pagamento" sem saber qual implementação recebe.

``javascript function criarNotificador(canal) { switch (canal) { case "email": return new NotificadorEmail(); case "sms": return new NotificadorSMS(); default: throw new Error(Canal desconhecido: ${canal}); } } ``

Use quando a decisão de qual tipo criar depende de configuração ou contexto, e quando você quer que o resto do sistema dependa de uma abstração, não da classe concreta. Evite quando só existe um tipo e nunca vai existir outro — aí a factory é cerimônia vazia.

Builder: montar objetos complexos por partes#

Quando um objeto tem muitos parâmetros, vários deles opcionais, o construtor vira uma lista ilegível de argumentos onde ninguém sabe o que é cada posição. O Builder monta o objeto passo a passo, com métodos nomeados, e produz o resultado no final. Ganha-se legibilidade e a possibilidade de validar a construção antes de entregar um objeto pela metade.

Use para objetos com muitas configurações opcionais. Evite para objetos simples de dois ou três campos, onde um construtor direto é mais claro.

Strategy: trocar o algoritmo sem trocar o resto#

Strategy encapsula uma família de comportamentos intercambiáveis atrás de uma interface comum, para que o algoritmo usado possa ser escolhido em tempo de execução. Em vez de um switch gigante que decide como calcular o frete para cada transportadora, cada estratégia é um objeto (ou função) que o código recebe e usa sem saber qual é.

``python def calcular_total(itens, estrategia_frete): subtotal = sum(i.preco for i in itens) return subtotal + estrategia_frete(subtotal) # a estratégia é injetada ``

Use quando há várias formas de fazer a mesma coisa e a escolha varia. Em linguagens com funções de primeira classe, a Strategy muitas vezes é só passar uma função — não precisa de hierarquia de classes.

Observer: avisar quem se interessa quando algo muda#

Observer estabelece uma relação de assinatura: um objeto (o sujeito) mantém uma lista de interessados e os notifica quando seu estado muda, sem saber quem eles são nem o que farão com o aviso. É a espinha dorsal de sistemas de eventos, da reatividade de interfaces e de qualquer situação em que "quando isso acontecer, várias coisas precisam reagir".

Use quando uma mudança em um lugar precisa desencadear reações desacopladas em outros. O cuidado é com o excesso: cadeias longas de observadores viram fluxos difíceis de rastrear, em que ninguém sabe mais o que dispara o quê.

Adapter: fazer duas interfaces incompatíveis conversarem#

Adapter é o encaixe entre um código que espera uma interface e um componente que oferece outra. Ele embrulha o segundo, traduzindo as chamadas, para que o primeiro o use sem modificação. É o padrão que você usa toda vez que integra uma biblioteca de terceiros cuja API não bate com a forma como seu sistema pensa.

Use para isolar dependências externas atrás de uma interface que você controla — o que, de quebra, facilita trocar a biblioteca depois. É um dos padrões mais úteis e menos abusados.

Decorator: adicionar comportamento sem alterar a classe#

Decorator envolve um objeto em outro que adiciona responsabilidade, mantendo a mesma interface. Assim você empilha comportamentos — logging, cache, compressão — em camadas, sem inchar a classe original com todas as combinações possíveis. Cada decorador acrescenta uma coisa, e eles se compõem.

Use quando você quer combinar funcionalidades opcionais de forma flexível. Evite quando as combinações são poucas e fixas — herança simples ou um parâmetro podem ser mais claros.

Repository: isolar o acesso a dados#

Embora não seja do catálogo original do GoF, o Repository é onipresente hoje. Ele oferece uma interface parecida com uma coleção — salvar, buscarPorId, remover — que esconde de onde os dados realmente vêm: banco relacional, API externa, memória. A lógica de negócio fala com o repositório e não sabe (nem quer saber) que existe SQL por baixo.

Use para desacoplar o domínio da tecnologia de persistência, o que torna o negócio testável com um repositório falso em memória. É um dos padrões que mais rende em manutenibilidade.

Singleton: o padrão que costuma ser abuso#

Singleton garante que uma classe tenha uma única instância acessível globalmente. Parece útil — configuração, conexão, log —, mas merece um alerta honesto: ele é, na prática, uma variável global disfarçada. Introduz estado compartilhado escondido, acopla tudo que o usa, e dificulta testes, porque não há como substituí-lo por uma versão controlada. Na maioria dos casos, injetar a dependência explicitamente resolve o mesmo problema sem os efeitos colaterais. Se você recorre ao Singleton, faça-o sabendo que está escolhendo conveniência a um custo de acoplamento.

Patternite: quando o padrão vira o problema#

Existe uma doença conhecida entre desenvolvedores que aprenderam padrões recentemente: a vontade de usá-los em tudo. O sintoma é uma base de código onde criar um objeto simples passa por três fábricas, uma interface e um builder, e onde ninguém entende o fluxo por baixo das camadas de abstração.

Padrão é resposta a um problema, não um objetivo. Aplicar Strategy onde só existe um algoritmo, ou Factory onde só existe um tipo, adiciona indireção sem adicionar valor — só custo de leitura. A pergunta certa nunca é "que padrão eu uso aqui?", e sim "que problema eu tenho?". Se não há problema de flexibilidade, acoplamento ou repetição, o código direto vence o código "padronizado".

Abstração prematura é dívida, não previdência. Muitos padrões existem para permitir variação futura — trocar de implementação, adicionar um novo tipo. Introduzi-los antes de a variação existir é apostar em um futuro que talvez não venha, pagando complexidade adiantada por flexibilidade que ninguém pediu. É mais barato adicionar o padrão quando o segundo caso realmente aparecer do que carregar a estrutura vazia esperando por ele. A maturidade com padrões se mede menos por quantos você conhece e mais por quantas vezes você resistiu a usá-los.

Um padrão mal aplicado é mais caro que nenhum. Vale lembrar que a indireção de um padrão tem um custo permanente de leitura: cada camada que se interpõe entre a intenção e a execução é mais um salto que quem lê precisa dar para entender o que acontece. Quando o padrão resolve um problema real, esse custo se paga; quando não, ele é puro imposto cognitivo cobrado de todo mundo que passar pelo código depois. Pior ainda é o padrão aplicado pela metade — uma Factory que na verdade só cria um tipo, um Observer com um único observador fixo —, que carrega toda a cerimônia da estrutura sem entregar nenhum de seus benefícios. Nesses casos, remover o padrão e voltar ao código direto é uma refatoração legítima, não um retrocesso. Reconhecer que a abstração errada saiu mais cara que a ausência dela é sinal de senioridade, não de fracasso.

Fechando#

Design patterns são um vocabulário que torna você mais fluente ao ler, escrever e discutir código — não uma lista de tarefas a cumprir. Aprenda a reconhecer os poucos que importam de verdade — Factory, Builder, Strategy, Observer, Adapter, Decorator, Repository — pelo problema que resolvem, e trate o Singleton com a desconfiança que ele merece. Acima de tudo, lembre que o melhor uso de um padrão é muitas vezes não usá-lo: a indireção que ele adiciona só se paga quando existe um problema real de flexibilidade, acoplamento ou repetição para justificar. Conhecer os padrões torna você um engenheiro melhor; saber quando não aplicá-los é o que separa quem entendeu de quem só decorou o catálogo.

Leituras relacionadas

Nenhum comentário ainda

Seja o primeiro a comentar.

Deixe seu comentário

Entre com sua conta Canverly para comentar. Você pode usar a mesma conta em qualquer site da rede.

Entrar com Canverly