Em 1994, Erich Gamma, Richard Helm, Ralph Johnson, e John Vlissides — os "Gang of Four" — publicaram Design Patterns: Elements of Reusable Object-Oriented Software. O livro catalogou 23 padrões que descreviam soluções recorrentes para problemas recorrentes em design de software orientado a objetos. Trinta anos depois, esses padrões ainda são o vocabulário mais amplamente usado para comunicar decisões de design entre engenheiros.
O perigo dos Design Patterns — que os próprios autores mencionam mas que frequentemente é ignorado — é usá-los como objetivo, não como ferramenta. Um padrão GoF não é uma solução que você aplica porque é elegante ou porque impressiona em code review. É um nome dado a uma solução que você já precisava para um problema específico. Reconhecer o problema é a habilidade; conhecer o padrão é o atalho para chegar à solução.
Padrões Creational — como objetos são criados
Padrões criacionais abstraem o processo de instanciação de objetos. Eles existem porque
criação direta via new acopla o código à classe concreta — o que torna testes
difíceis, extensão cara, e configuração inflexível.
Factory Method. Define uma interface para criar um objeto, mas deixa as
subclasses ou implementações concretas decidirem qual classe instanciar. O problema que resolve:
o código que usa o objeto não deve saber (ou não pode saber em tempo de compilação) qual
implementação concreta usar. Um IPaymentProcessor Create(string provider) que
retorna StripeProcessor ou PayPalProcessor dependendo do provider
é Factory Method. O benefício: adicionar um novo provider não muda o código que usa o processador.
Abstract Factory. Provê uma interface para criar famílias de objetos relacionados sem especificar suas classes concretas. O problema: você precisa de objetos que sejam compatíveis entre si — como componentes de UI que precisam ser todos do mesmo tema (dark/light), ou conectores de banco que precisam usar o mesmo dialeto SQL. Abstract Factory garante que você nunca mistura objetos de famílias incompatíveis.
Builder. Separa a construção de um objeto complexo da sua representação.
Permite criar o mesmo tipo de objeto com diferentes representações. O problema que resolve:
construtores com muitos parâmetros opcionais (telescoping constructor anti-pattern). Em vez
de new Order(customerId, null, null, false, true, null, "BRL"), você tem
new OrderBuilder().ForCustomer(id).WithCurrency("BRL").Build(). Em linguagens
com named parameters (Python, C# com records), Builder é frequentemente desnecessário.
Prototype. Cria novos objetos copiando um objeto existente. Útil quando criar um objeto é caro (requer dados de banco, configuração complexa) e você precisa de muitas instâncias semelhantes. Em linguagens modernas, a questão de shallow vs. deep copy é o problema mais comum de implementação.
Singleton. Garante que uma classe tenha apenas uma instância. É o padrão GoF mais abusado. Os problemas são bem documentados: estado global implícito, testes difíceis (a instância única carrega estado entre testes), dependências ocultas. Em código moderno, injeção de dependência com escopo de singleton (o container gerencia a instância) é preferível ao Singleton clássico. Use Singleton explícito apenas quando o invariante de instância única tem significado de domínio — não apenas por conveniência de acesso global.
Padrões Structural — como objetos se compõem
Padrões estruturais lidam com composição de classes e objetos para criar estruturas maiores.
Adapter. Converte a interface de uma classe em outra interface que o cliente espera. O problema: você tem código que depende de uma interface, e uma biblioteca ou serviço que provê a funcionalidade certa mas com uma interface diferente. O Adapter é a ponte. É o padrão central do Anti-Corruption Layer em DDD estratégico — o ACL é um Adapter que converte o vocabulário do sistema externo para o vocabulário interno.
Facade. Provê uma interface simplificada para um subsistema complexo.
O problema: o cliente precisa orquestrar múltiplos objetos de um subsistema para realizar
uma operação. A Facade esconde essa complexidade atrás de uma interface única e de alto nível.
EmailService.Send(to, subject, body) é uma Facade — internamente pode estar
formatando templates, gerenciando conexão SMTP, tratando retry, e logando. O benefício:
o cliente não precisa saber de nada disso.
Proxy. Provê um substituto ou placeholder para outro objeto, controlando o acesso a ele. Há vários tipos de proxy com propósitos distintos: Virtual Proxy (adia a inicialização cara de um objeto até ser necessário), Remote Proxy (representa um objeto em outro processo ou máquina), Protection Proxy (controla quem pode acessar o objeto), Caching Proxy (armazena resultados de operações caras). O Proxy tem a mesma interface do objeto que representa — o cliente não sabe que está falando com um proxy.
Decorator. Adiciona responsabilidades a um objeto dinamicamente, sem modificar a classe. A alternativa à herança para extensão de comportamento. O problema que resolve: você tem um objeto com comportamento X e precisa de uma versão com X + Y sem criar uma subclasse para cada combinação. Stack de middlewares em ASP.NET, pipes em FastAPI, interceptors em Go — todos são implementações do padrão Decorator.
Composite. Compõe objetos em estruturas de árvore para representar hierarquias parte-todo. Permite tratar objetos individuais e composições uniformemente. O problema: você tem uma estrutura recursiva (sistema de arquivos, menus, regras de desconto aninhadas) onde operações precisam funcionar tanto em folhas quanto em nós.
Bridge. Desacopla uma abstração de sua implementação, permitindo que as duas variem independentemente. Diferente do Adapter (que conecta interfaces incompatíveis existentes), Bridge é uma decisão de design preventiva: você cria a abstração sabendo que haverá múltiplas implementações.
Exemplos em três linguagens
// Factory Method — criação desacoplada da classe concreta
public interface IPaymentProcessor { Task<PaymentResult> ChargeAsync(decimal amount, string token); }
public static class PaymentProcessorFactory
{
public static IPaymentProcessor Create(string provider) => provider switch
{
"stripe" => new StripeProcessor(),
"paypal" => new PayPalProcessor(),
_ => throw new ArgumentException($"Provider desconhecido: {provider}")
};
}
// Builder — construção fluente de objetos complexos
public class EmailBuilder
{
private string _to = "", _subject = "", _body = "";
private bool _isHtml;
private readonly List<string> _cc = new();
public EmailBuilder To(string address) { _to = address; return this; }
public EmailBuilder Subject(string s) { _subject = s; return this; }
public EmailBuilder Body(string b, bool html = false) { _body = b; _isHtml = html; return this; }
public EmailBuilder Cc(string address) { _cc.Add(address); return this; }
public Email Build()
{
if (string.IsNullOrEmpty(_to)) throw new InvalidOperationException("Destinatário obrigatório");
return new Email(_to, _subject, _body, _isHtml, _cc.AsReadOnly());
}
}
// Uso: new EmailBuilder().To("a@b.com").Subject("Olá").Body("Teste").Build()
// Decorator — adiciona comportamento sem modificar a classe
public interface IOrderRepository { Task<Order?> FindByIdAsync(OrderId id); }
public class CachingOrderRepository : IOrderRepository // Decorator
{
private readonly IOrderRepository _inner;
private readonly IMemoryCache _cache;
public CachingOrderRepository(IOrderRepository inner, IMemoryCache cache)
{
_inner = inner;
_cache = cache;
}
public async Task<Order?> FindByIdAsync(OrderId id)
{
var key = $"order:{id.Value}";
if (_cache.TryGetValue(key, out Order? cached)) return cached;
var order = await _inner.FindByIdAsync(id);
if (order is not null) _cache.Set(key, order, TimeSpan.FromMinutes(5));
return order;
}
}
// Registro — cliente vê IOrderRepository, não sabe que é Decorator
services.AddScoped<PostgresOrderRepository>();
services.AddScoped<IOrderRepository>(sp =>
new CachingOrderRepository(
sp.GetRequiredService<PostgresOrderRepository>(),
sp.GetRequiredService<IMemoryCache>()
));
// Facade — API simplificada sobre subsistema complexo
public class NotificationFacade
{
private readonly IEmailSender _email;
private readonly IPushNotifier _push;
private readonly ISmsClient _sms;
public async Task SendOrderConfirmationAsync(Order order, Customer customer)
{
var tasks = new List<Task>();
if (customer.WantsEmail)
tasks.Add(_email.SendAsync(customer.Email, "Pedido confirmado", BuildEmailBody(order)));
if (customer.WantsPush)
tasks.Add(_push.NotifyAsync(customer.DeviceToken, "Pedido #" + order.Id.Value + " confirmado"));
await Task.WhenAll(tasks);
}
}
from abc import ABC, abstractmethod
from functools import wraps
# Factory Method — retorna implementação correta baseada em configuração
class PaymentProcessor(ABC):
@abstractmethod
async def charge(self, amount: float, token: str) -> dict: ...
class StripeProcessor(PaymentProcessor):
async def charge(self, amount: float, token: str) -> dict:
# ... chamada à Stripe API
return {"status": "success", "provider": "stripe"}
class PayPalProcessor(PaymentProcessor):
async def charge(self, amount: float, token: str) -> dict:
return {"status": "success", "provider": "paypal"}
def create_payment_processor(provider: str) -> PaymentProcessor:
processors = {"stripe": StripeProcessor, "paypal": PayPalProcessor}
cls = processors.get(provider)
if not cls:
raise ValueError(f"Provider desconhecido: {provider}")
return cls()
# Builder — em Python, dataclasses com defaults cobrem muitos casos; use Builder quando
# a construção tem múltiplas etapas com validação intermédia
class QueryBuilder:
def __init__(self): self._parts: list[str] = []
def select(self, *cols: str) -> "QueryBuilder":
self._parts.append(f"SELECT {', '.join(cols)}"); return self
def from_(self, table: str) -> "QueryBuilder":
self._parts.append(f"FROM {table}"); return self
def where(self, cond: str) -> "QueryBuilder":
self._parts.append(f"WHERE {cond}"); return self
def build(self) -> str:
return " ".join(self._parts)
# Decorator — usando classes ou funções; Python facilita com __call__
class LoggingOrderRepository:
def __init__(self, inner, logger):
self._inner = inner
self._log = logger
async def find_by_id(self, order_id: str):
self._log.info(f"Finding order {order_id}")
result = await self._inner.find_by_id(order_id)
self._log.info(f"Found: {result is not None}")
return result
class CachingOrderRepository:
def __init__(self, inner, cache):
self._inner = inner
self._cache = cache
async def find_by_id(self, order_id: str):
if cached := self._cache.get(order_id):
return cached
result = await self._inner.find_by_id(order_id)
if result:
self._cache.set(order_id, result, ttl=300)
return result
# Compondo Decorators — cliente não sabe da composição
repo = CachingOrderRepository(
LoggingOrderRepository(PostgresOrderRepository(pool), logger),
cache
)
# Facade — simplifica chamadas a múltiplos serviços
class NotificationFacade:
def __init__(self, email, push, sms):
self._email = email; self._push = push; self._sms = sms
async def send_order_confirmation(self, order: "Order", customer: "Customer") -> None:
import asyncio
tasks = []
if customer.wants_email:
tasks.append(self._email.send(customer.email, "Pedido confirmado", order))
if customer.wants_push:
tasks.append(self._push.notify(customer.device_token, f"Pedido {order.id} confirmado"))
await asyncio.gather(*tasks)
// Factory Method — função que retorna interface
package payment
type Processor interface {
Charge(ctx context.Context, amount float64, token string) (Result, error)
}
func NewProcessor(provider string) (Processor, error) {
switch provider {
case "stripe": return &StripeProcessor{}, nil
case "paypal": return &PayPalProcessor{}, nil
default: return nil, fmt.Errorf("provider desconhecido: %s", provider)
}
}
// Builder — útil em Go para structs com muitos campos opcionais
type EmailMessage struct {
To, Subject, Body string
CC []string
IsHTML bool
}
type EmailBuilder struct{ msg EmailMessage }
func (b *EmailBuilder) To(addr string) *EmailBuilder { b.msg.To = addr; return b }
func (b *EmailBuilder) Subject(s string) *EmailBuilder { b.msg.Subject = s; return b }
func (b *EmailBuilder) Body(body string) *EmailBuilder { b.msg.Body = body; return b }
func (b *EmailBuilder) HTML() *EmailBuilder { b.msg.IsHTML = true; return b }
func (b *EmailBuilder) CC(addr string) *EmailBuilder { b.msg.CC = append(b.msg.CC, addr); return b }
func (b *EmailBuilder) Build() (EmailMessage, error) {
if b.msg.To == "" { return EmailMessage{}, errors.New("destinatário obrigatório") }
return b.msg, nil
}
// Decorator — implementa a mesma interface que o componente base
type OrderRepository interface {
FindByID(ctx context.Context, id string) (*Order, error)
Save(ctx context.Context, o *Order) error
}
type cachingOrderRepo struct {
inner OrderRepository
cache Cache
}
func WithCache(inner OrderRepository, cache Cache) OrderRepository {
return &cachingOrderRepo{inner: inner, cache: cache}
}
func (r *cachingOrderRepo) FindByID(ctx context.Context, id string) (*Order, error) {
if v := r.cache.Get(id); v != nil { return v.(*Order), nil }
order, err := r.inner.FindByID(ctx, id)
if err == nil && order != nil { r.cache.Set(id, order, 5*time.Minute) }
return order, err
}
func (r *cachingOrderRepo) Save(ctx context.Context, o *Order) error {
r.cache.Delete(o.ID) // invalida cache na escrita
return r.inner.Save(ctx, o)
}
// Facade — esconde complexidade de múltiplos serviços
type NotificationService struct {
email EmailSender
push PushNotifier
}
func (s *NotificationService) SendOrderConfirmation(ctx context.Context, order *Order, customer *Customer) error {
eg, ctx := errgroup.WithContext(ctx)
if customer.WantsEmail {
eg.Go(func() error { return s.email.Send(ctx, customer.Email, "Pedido confirmado", order.ID) })
}
if customer.WantsPush {
eg.Go(func() error { return s.push.Notify(ctx, customer.DeviceToken, order.ID) })
}
return eg.Wait()
}
Patterns emergem, não são impostos
O processo saudável de usar Design Patterns é: você tem um problema concreto no código — acoplamento que dificulta teste, repetição de lógica, dificuldade de extensão. Você refatora. Ao revisar a refatoração, você (ou alguém da equipe) reconhece "isso é um Decorator" ou "isso é Factory Method". O nome facilita a comunicação — "extrai para um Decorator aqui" é mais rápido de dizer do que descrever a estrutura completa.
O processo anti-saudável: você decide que vai usar Factory Method antes de ter o problema, cria uma fábrica para um objeto que só tem uma implementação e nunca vai ter outra, e adiciona uma camada de indireção sem benefício. O código fica mais difícil de entender sem ganho algum de flexibilidade.
Referências
- Gamma, E. et al. — Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994. O livro original — ainda vale ler os caps de cada padrão.
- Freeman, E.; Freeman, E. — Head First Design Patterns, 2ª ed. O'Reilly, 2020. A versão mais acessível com exemplos visuais.
- Fowler, M. — Patterns of Enterprise Application Architecture. Addison-Wesley, 2002. Padrões em contexto de aplicações enterprise.
- Refactoring.Guru — refactoring.guru/design-patterns. Referência visual completa de todos os padrões GoF.
- Seemann, M. — Dependency Injection in .NET. Manning, 2011. Factory e Decorator em contexto de DI.
- Martin, R. C. — Agile Software Development. Prentice Hall, 2002. SOLID como fundamento para quando aplicar cada padrão.
- Kerievsky, J. — Refactoring to Patterns. Addison-Wesley, 2004. Como chegar aos padrões via refatoração — a perspectiva correta.
- Fowler, M. — Refactoring, 2ª ed. Addison-Wesley, 2018. As refatorações que levam a padrões emergentes.
- Holub, A. — "Why Getter And Setter Methods Are Evil". JavaWorld, 2003. O argumento contra Anemic Domain Model que justifica OOP rica.
- Wlaschin, S. — Domain Modeling Made Functional. Pragmatic Bookshelf, 2018. Como padrões funcionais substituem alguns GoF.
- Nesteruk, D. — Design Patterns in .NET Core. Apress, 2019. Implementações modernas dos padrões GoF.
- Shvets, A. — Dive Into Design Patterns. Refactoring.Guru, 2021. A versão livro da referência online.