Em que consiste um padrão de projeto?

Um padrão de projeto consiste em um conjunto bem definido de elementos que descrevem como resolver um problema recorrente de design de software dentro de um determinado contexto. Ele não se limita à solução em si, porque também explica quando utilizá-la, por que ela funciona e quais impactos ela gera no sistema.

Neste artigo, você vai conhecer cada um desses elementos em detalhe. Além disso, vai ver um exemplo em C# que mostra como eles aparecem no código real.

O que é um padrão de projeto

Um padrão de projeto, ou design pattern, é uma solução típica para um problema que aparece com frequência no design de software. Em vez de um trecho de código pronto, ele funciona como uma planta que você adapta ao seu cenário.

A ideia nasceu fora da computação. O arquiteto Christopher Alexander descreveu padrões para construções e cidades, e definiu cada padrão como a descrição de um problema que ocorre repetidamente e do núcleo da sua solução.

Segundo Alexander, essa solução pode ser usada um milhão de vezes, sem nunca ser aplicada exatamente da mesma forma. Essa frase resume bem o espírito de um padrão de projeto.

Em 1987, Kent Beck e Ward Cunningham levaram esse conceito para a programação e apresentaram o trabalho na conferência OOPSLA. Em seguida, em 1994, Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides publicaram o livro "Design Patterns: Elements of Reusable Object-Oriented Software".

Esse livro catalogou 23 padrões e popularizou o apelido Gang of Four, ou GoF. Se você quer conhecer essa trajetória em detalhe, vale ler o artigo sobre a história dos design patterns.

Os elementos de um padrão de projeto

O livro do GoF afirma que, em geral, um padrão tem quatro elementos essenciais: nome, problema, solução e consequências. Neste artigo, destacamos o contexto como um elemento separado, porque ele define quando o padrão faz sentido.

Assim, um padrão de projeto é composto pelos seguintes elementos:

  • O problema recorrente.
  • O contexto em que ele aparece.
  • A solução conceitual.
  • As consequências da escolha.
  • O vocabulário comum, representado pelo nome do padrão.

Cada elemento responde a uma pergunta diferente. Juntos, eles transformam uma boa ideia isolada em conhecimento que qualquer equipe consegue reaproveitar.

O problema

Todo padrão de projeto começa com a descrição de um problema recorrente, que aparece com frequência no desenvolvimento de software. Normalmente, esse problema está relacionado a um destes temas:

  • Criação de objetos.
  • Variação de comportamentos.
  • Comunicação entre componentes.
  • Organização estrutural do sistema.

O padrão deixa claro em quais situações o problema ocorre e quais limitações existem no cenário apresentado. Por isso, a descrição do problema vem antes de qualquer diagrama ou classe.

Um bom exemplo é o problema de um sistema que precisa calcular frete de formas diferentes. Se cada nova transportadora exige um novo if dentro do mesmo método, o código fica frágil e difícil de testar.

Na prática, reconhecer o problema é a parte mais difícil. Afinal, aplicar um padrão de projeto sem ter o problema que ele resolve só adiciona complexidade.

O contexto

O contexto define quando o padrão faz sentido. Nem toda solução serve para todos os cenários, e os bons padrões deixam isso explícito.

Aqui entram fatores como:

  • Restrições de arquitetura.
  • Necessidade de extensibilidade.
  • Nível de acoplamento aceitável.
  • Frequência de mudanças no código.

Esse elemento evita o uso indiscriminado de padrões apenas porque eles parecem elegantes. Um sistema pequeno, que muda pouco, raramente precisa da mesma estrutura de um sistema que recebe novas regras toda semana.

No formato do GoF, o contexto aparece principalmente na seção de aplicabilidade. Ela lista as situações em que o padrão de projeto é indicado e, de forma indireta, aquelas em que ele não é.

A solução

A solução descreve uma estrutura geral de classes e objetos, mostrando como eles se relacionam e colaboram para resolver o problema. Contudo, o padrão não define uma implementação exata.

Em vez disso, ele entrega um esqueleto de solução que você adapta conforme a linguagem e o domínio. Por exemplo, um padrão pode sugerir:

  • Introduzir uma interface.
  • Delegar comportamento.
  • Encapsular responsabilidades.
  • Separar criação de uso.

Essa característica diferencia um padrão de projeto de um algoritmo. Enquanto o algoritmo define uma sequência clara de passos, o padrão descreve a forma da solução e deixa os detalhes para quem implementa.

Muitas soluções do GoF seguem dois princípios citados no próprio livro. O primeiro diz para programar para uma interface, e não para uma implementação; o segundo recomenda favorecer a composição de objetos em vez da herança de classes.

As consequências

Todo padrão de projeto apresenta consequências claras, tanto positivas quanto negativas. Elas ajudam o desenvolvedor a entender os trade-offs envolvidos na escolha.

Entre as consequências mais comuns estão:

  • Aumento de flexibilidade.
  • Redução de acoplamento.
  • Maior número de classes.
  • Aumento de abstrações.

Repare que a lista mistura ganhos e custos. Na prática, nenhum padrão é gratuito, porque toda abstração extra cobra um preço em leitura e manutenção.

Esse elemento é fundamental para decisões conscientes de design. Ele permite comparar alternativas antes de escrever o código, e não depois que o problema já apareceu em produção.

O vocabulário comum

Um padrão também consiste em um nome e em uma linguagem compartilhada. Ao identificar um design pattern no código, os desenvolvedores conseguem compreender rapidamente:

  • A intenção da solução.
  • O tipo de problema que está sendo tratado.
  • As possíveis extensões ou variações.

Dizer que um trecho de código utiliza Factory, Strategy ou Observer é uma forma compacta de comunicar design. Em poucas palavras, você transmite uma estrutura inteira de classes e responsabilidades.

Esse vocabulário também acelera revisões de código e conversas de arquitetura. Por isso, mesmo quem não aplica um padrão de projeto todos os dias ganha ao conhecer os nomes mais comuns.

Um padrão de projeto na prática com Strategy

Para tornar os elementos concretos, vamos usar o Strategy, um dos padrões comportamentais do GoF. A seguir, cada parte do exemplo mostra um elemento em ação.

Problema e contexto do exemplo

Imagine um serviço de pedidos que calcula o frete com base na transportadora escolhida. A primeira versão costuma ficar assim:

public class ShippingService
{
    public decimal Calculate(string carrier, decimal weight)
    {
        // cada nova transportadora exige mais um if neste método
        if (carrier == "standard")
            return weight * 1.5m;

        if (carrier == "express")
            return weight * 3.0m + 10m;

        throw new ArgumentException("Unknown carrier");
    }
}

O problema é a variação de comportamento: a regra de cálculo muda conforme a transportadora. Já o contexto indica que novas transportadoras chegam com frequência e que cada regra precisa de testes isolados.

Solução em C#

O Strategy propõe encapsular cada comportamento em uma classe própria, atrás de uma interface comum. Assim, o serviço passa a delegar o cálculo, em vez de decidir tudo sozinho.

public interface IShippingStrategy
{
    string Carrier { get; }
    decimal Calculate(decimal weight);
}

public class StandardShipping : IShippingStrategy
{
    public string Carrier => "standard";
    public decimal Calculate(decimal weight) => weight * 1.5m;
}

public class ExpressShipping : IShippingStrategy
{
    public string Carrier => "express";
    public decimal Calculate(decimal weight) => weight * 3.0m + 10m;
}

public class ShippingService(IEnumerable<IShippingStrategy> strategies)
{
    public decimal Calculate(string carrier, decimal weight)
    {
        // o serviço apenas escolhe a estratégia e delega o cálculo
        var strategy = strategies.FirstOrDefault(s => s.Carrier == carrier)
            ?? throw new ArgumentException("Unknown carrier");

        return strategy.Calculate(weight);
    }
}

Repare que o código segue os princípios citados antes. O serviço depende da interface IShippingStrategy e recebe as estratégias por composição, e não por herança.

Com injeção de dependência, basta registrar cada estratégia no container. Se você quer revisar esse tema, o artigo sobre AddTransient, AddScoped e AddSingleton explica os tempos de vida dos serviços.

Consequências no exemplo

Do lado positivo, adicionar uma transportadora significa criar uma nova classe, sem tocar no ShippingService. Além disso, cada estratégia pode ganhar testes unitários independentes.

Por outro lado, o projeto agora tem três classes e uma interface no lugar de um único método. Para um sistema com duas regras que nunca mudam, esse custo talvez não compense.

Por fim, o nome do padrão cumpre o papel de vocabulário comum. Quando alguém diz que o frete usa Strategy, a equipe entende a estrutura sem abrir o código.

Como um padrão de projeto é documentado

O livro do GoF descreve cada padrão com um formato fixo de seções. Esse formato mostra, na prática, em que consiste um padrão de projeto completo.

As seções são:

  • Nome e classificação.
  • Intenção.
  • Também conhecido como.
  • Motivação.
  • Aplicabilidade.
  • Estrutura.
  • Participantes.
  • Colaborações.
  • Consequências.
  • Implementação.
  • Exemplo de código.
  • Usos conhecidos.
  • Padrões relacionados.

Note que o código aparece quase no fim. Antes dele, o formato dedica várias seções ao problema, ao contexto e às consequências.

Catálogos mais recentes, como o Refactoring Guru, usam versões resumidas desse formato. Mesmo assim, mantêm intenção, motivação, estrutura e exemplo de código como base.

As três famílias de padrões

O GoF organizou os 23 padrões em três categorias, de acordo com o tipo de problema que resolvem. Essa classificação também faz parte do primeiro elemento da documentação, junto com o nome.

Os padrões criacionais tratam da criação de objetos. Nessa família estão Abstract Factory, Builder, Factory Method, Prototype e Singleton.

Os padrões estruturais tratam da composição de classes e objetos. Aqui aparecem Adapter, Bridge, Composite, Decorator, Facade, Flyweight e Proxy.

Já os padrões comportamentais tratam da comunicação e da distribuição de responsabilidades. A lista inclui Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method e Visitor.

Para uma visão geral de cada categoria, o artigo o que é um design pattern complementa bem este conteúdo.

Padrão de projeto não é algoritmo nem framework

Uma confusão comum é tratar o padrão como uma receita fechada. No entanto, o algoritmo descreve passos exatos, enquanto o padrão descreve uma forma de organizar responsabilidades.

O catálogo do Refactoring Guru usa uma analogia útil. O algoritmo se parece com uma receita de cozinha, e o padrão se parece com uma planta, que mostra o resultado esperado sem fixar cada detalhe da construção.

Um framework, por sua vez, é código pronto que você reutiliza. Muitas vezes, ele aplica vários padrões internamente, mas o padrão de projeto em si continua sendo uma ideia, e não uma biblioteca.

O próprio ASP.NET Core é um exemplo disso. A injeção de dependência, o pipeline de middlewares e o Options pattern aplicam ideias de design já conhecidas dentro do framework.

Como escolher um padrão de projeto

Os elementos de um padrão também servem como roteiro de decisão. Por isso, vale percorrer cada um deles antes de escrever a primeira classe.

Primeiro, descreva o problema com as suas palavras, sem citar nenhum padrão. Por exemplo, diga que o cálculo de frete muda por transportadora, e não que você precisa de um Strategy.

Em seguida, avalie o contexto. Pergunte com que frequência esse trecho muda, quantas variações existem hoje e quantas devem surgir nos próximos meses.

Depois disso, compare as soluções candidatas. Muitas vezes, dois padrões resolvem o mesmo problema, então o contexto decide qual deles encaixa melhor.

Então, liste as consequências de cada opção. Considere o número de classes novas, o impacto nos testes e o esforço de quem vai ler o código depois de você.

Por fim, registre a decisão usando o nome do padrão. Dessa forma, o vocabulário comum fica documentado no pull request ou no registro de decisão de arquitetura, e a equipe entende o motivo da escolha.

Esse roteiro mostra que conhecer um padrão de projeto vai além de decorar diagramas. Na verdade, o valor está em saber quando a solução cabe no problema.

Quando evitar um padrão de projeto

Como todo padrão tem consequências, existem situações em que ele atrapalha. O caso mais comum é aplicar o padrão antes de o problema existir.

Esse excesso costuma gerar interfaces com uma única implementação e fábricas que criam apenas um tipo de objeto. Nesse caso, o código fica mais longo sem ficar mais flexível.

Também existe uma crítica conhecida ao catálogo do GoF. Peter Norvig observou que 16 dos 23 padrões ficam mais simples ou desaparecem em linguagens dinâmicas como Lisp e Dylan, porque a linguagem já oferece o recurso.

O C# moderno confirma parte dessa visão. Delegates, lambdas e records tornam algumas estruturas clássicas bem mais leves, então o padrão continua existindo, mas com menos código.

Por isso, a pergunta certa não é qual padrão usar, e sim qual problema você tem. Princípios como os do SOLID ajudam a identificar esse problema antes de escolher a solução.

Em resumo

Um padrão de projeto consiste em problema recorrente, contexto bem definido, solução conceitual, consequências explícitas e vocabulário comum. O livro do GoF formaliza quatro desses elementos e trata o contexto dentro do problema e da aplicabilidade.

Juntos, esses elementos transformam experiência prática em conhecimento reutilizável. Com isso, você consegue criar sistemas mais claros, flexíveis e preparados para evoluir.

O mais importante é lembrar que o padrão de projeto começa pelo problema. Quando você entende o problema e o contexto, a solução e as consequências passam a fazer sentido, e o nome do padrão vira apenas o atalho para comunicar a decisão.

FAQ

Em que consiste um padrão de projeto?

Consiste em um problema recorrente, no contexto em que ele aparece, em uma solução conceitual, nas consequências dessa solução e em um nome compartilhado. Esses elementos explicam o que fazer, quando fazer e qual o custo da escolha.

Quais são os quatro elementos essenciais segundo o GoF?

O livro do GoF cita nome do padrão, problema, solução e consequências. O contexto aparece dentro da descrição do problema e da seção de aplicabilidade.

Padrão de projeto é a mesma coisa que algoritmo?

Não. O algoritmo define passos exatos, enquanto o padrão descreve uma estrutura de solução que você adapta ao seu código.

Quantos padrões existem no livro do GoF?

O livro cataloga 23 padrões, divididos em criacionais, estruturais e comportamentais.

Preciso usar padrões em todo projeto?

Não. Use um padrão apenas quando o problema que ele resolve existir de fato, porque toda solução traz custos em número de classes e abstrações.

Glossário

Termo Significado
Padrão de projeto Solução reutilizável para um problema recorrente de design de software
GoF Gang of Four, apelido dos quatro autores do livro Design Patterns, de 1994
Contexto Conjunto de condições em que um padrão faz sentido
Consequências Efeitos positivos e negativos de aplicar um padrão
Acoplamento Grau de dependência entre dois componentes de software
Strategy Padrão comportamental que encapsula algoritmos intercambiáveis atrás de uma interface
Composição Técnica de montar comportamento combinando objetos em vez de herdar classes
Trade-off Troca entre um benefício e um custo em uma decisão de design
OOPSLA Conferência sobre programação orientada a objetos onde Beck e Cunningham apresentaram padrões em 1987

Fontes