Padrões de projeto (design patterns) são soluções reutilizáveis para problemas recorrentes no desenvolvimento de software. Eles funcionam como plantas arquiteturais testadas e aprovadas pela comunidade, ajudando você a escrever código mais organizado, flexível e de fácil manutenção. Em Python, esses padrões ganham ainda mais expressividade graças aos recursos dinâmicos da linguagem, como funções de primeira classe, decoradores e metaclasses.
Neste guia completo, você vai aprender os padrões de projeto mais importantes aplicados ao Python: desde os criacionais como Singleton e Factory até os comportamentais como Strategy e Observer. Cada padrão é explicado com exemplos reais de código que você pode usar imediatamente nos seus projetos. Vamos começar!
O Que São Design Patterns?
O conceito de design patterns foi popularizado pelo livro Design Patterns: Elements of Reusable Object-Oriented Software, conhecido como Gang of Four (GoF), publicado em 1994 por Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides. A obra original da Gang of Four definiu 23 padrões que se tornaram referência mundial no desenvolvimento orientado a objetos.
Os padrões são divididos em três categorias principais:
- Padrões Criacionais — lidam com a criação de objetos de forma flexível e desacoplada
- Padrões Estruturais — organizam a composição de classes e objetos para formar estruturas maiores
- Padrões Comportamentais — definem como objetos interagem e distribuem responsabilidades entre si
O repositório python-patterns no GitHub mantido pela comunidade contém implementações de dezenas de padrões em Python puro e serve como excelente referência prática para consulta.
Por Que Usar Design Patterns em Python?
Python é uma linguagem multiparadigma que suporta programação orientada a objetos, funcional e procedural. Essa flexibilidade torna a implementação de design patterns especialmente elegante. Por exemplo, enquanto em Java você precisa de classes e interfaces para implementar o padrão Strategy, em Python você pode usar funções diretamente, graças ao suporte a funções de primeira classe.
A documentação oficial do Python reforça que a simplicidade e legibilidade são princípios fundamentais da linguagem. Os design patterns, quando bem aplicados, contribuem justamente para esses princípios ao oferecer soluções padronizadas que outros desenvolvedores reconhecem imediatamente.
Antes de mergulharmos nos padrões, é importante ter uma base sólida em Programação Orientada a Objetos em Python, pois a maioria dos padrões GoF utiliza conceitos como classes, herança, polimorfismo e composição.
Padrões Criacionais
Padrões criacionais abstraem o processo de instanciação de objetos, tornando o sistema independente de como seus objetos são criados, compostos e representados. Vamos explorar os três mais utilizados no ecossistema Python.
Singleton
O padrão Singleton garante que uma classe tenha apenas uma instância durante toda a execução do programa e fornece um ponto de acesso global a ela. É amplamente utilizado para gerenciar conexões de banco de dados, configurações de aplicação e logs.
Em Python, existem várias formas de implementar Singleton. A mais pitônica utiliza metaclasses ou o módulo threading para segurança em ambientes concorrentes:
import threading
class SingletonMeta(type):
_instances = {}
_lock = threading.Lock()
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
with cls._lock:
if cls not in cls._instances:
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance
return cls._instances[cls]
class Configuracao(metaclass=SingletonMeta):
def init(self):
self.config = {}
def definir(self, chave, valor):
self.config[chave] = valor
def obter(self, chave):
return self.config.get(chave)
Testando o Singleton
c1 = Configuracao()
c2 = Configuracao()
c1.definir("debug", True)
print(c2.obter("debug")) # True — mesma instância
print(c1 is c2) # True
Uma alternativa ainda mais simples em Python é usar o módulo como singleton. Como módulos em Python são carregados apenas uma vez, qualquer variável definida no módulo funciona como um singleton natural:
# config.py
class _Config:
def __init__(self):
self.debug = False
self.database_url = "sqlite:///app.db"
config = _Config() # importada uma vez, instância única
O tutorial do Real Python sobre Singletons explora outras abordagens, incluindo decoradores e a biblioteca monostate.
Factory Method
O Factory Method define uma interface para criar objetos, mas permite que as subclasses decidam qual classe instanciar. Ele é útil quando o tipo exato do objeto só é conhecido em tempo de execução.
from abc import ABC, abstractmethod
class Notificador(ABC):
@abstractmethod
def enviar(self, mensagem: str) -> bool:
pass
class EmailNotificador(Notificador):
def enviar(self, mensagem: str) -> bool:
print(f"Enviando email: {mensagem}")
return True
class SMSNotificador(Notificador):
def enviar(self, mensagem: str) -> bool:
print(f"Enviando SMS: {mensagem}")
return True
class PushNotificador(Notificador):
def enviar(self, mensagem: str) -> bool:
print(f"Enviando push: {mensagem}")
return True
class NotificadorFactory:
@staticmethod
def criar(tipo: str) -> Notificador:
tipos = {
"email": EmailNotificador,
"sms": SMSNotificador,
"push": PushNotificador,
}
classe = tipos.get(tipo)
if not classe:
raise ValueError(f"Tipo desconhecido: {tipo}")
return classe()
Uso
notificador = NotificadorFactory.criar("email")
notificador.enviar("Bem-vindo ao sistema!")
O uso do módulo abc (Abstract Base Classes) da biblioteca padrão é recomendado para definir contratos claros. A documentação oficial do módulo abc detalha como criar classes abstratas de forma elegante em Python.
Builder
O padrão Builder separa a construção de um objeto complexo da sua representação final, permitindo que o mesmo processo de construção possa criar diferentes representações. É ideal para objetos com muitos parâmetros opcionais ou configurações complexas.
class Pizza:
def __init__(self):
self.tamanho = None
self.massa = None
self.ingredientes = []
self.queijo_extra = False
self.borda_recheada = False
def __str__(self):
return (f"Pizza {self.tamanho}, massa {self.massa}, "
f"ingredientes: {', '.join(self.ingredientes)}, "
f"queijo extra: {self.queijo_extra}, "
f"borda recheada: {self.borda_recheada}")
class PizzaBuilder:
def init(self):
self._pizza = Pizza()
def com_tamanho(self, tamanho: str):
self._pizza.tamanho = tamanho
return self
def com_massa(self, massa: str):
self._pizza.massa = massa
return self
def adicionar_ingrediente(self, ingrediente: str):
self._pizza.ingredientes.append(ingrediente)
return self
def com_queijo_extra(self):
self._pizza.queijo_extra = True
return self
def com_borda_recheada(self):
self._pizza.borda_recheada = True
return self
def construir(self) -> Pizza:
return self._pizza
Uso
pizza = (PizzaBuilder()
.com_tamanho("Grande")
.com_massa("Fina")
.adicionar_ingrediente("Mussarela")
.adicionar_ingrediente("Calabresa")
.adicionar_ingrediente("Azeitona")
.com_queijo_extra()
.construir())
print(pizza)
Observe como o Builder retorna self em cada método, permitindo o encadeamento fluente de chamadas — uma técnica conhecida como method chaining que torna o código muito mais expressivo.
Padrões Estruturais
Padrões estruturais explicam como montar objetos e classes em estruturas maiores, garantindo flexibilidade e eficiência. Vamos ver os mais relevantes para Python.
Adapter
O Adapter permite que objetos com interfaces incompatíveis colaborem entre si. Ele atua como um "adaptador" que traduz chamadas de uma interface para outra — exatamente como um adaptador de tomadas no mundo real.
class SistemaAntigo:
def requisicao_antiga(self, dados: dict) -> str:
return f"Processando {dados.get('nome')} via sistema antigo"
class SistemaNovo:
def requisicao_nova(self, nome: str, versao: int) -> str:
return f"Processando {nome} v{versao} via sistema novo"
class AdapterSistema:
def init(self, sistema_novo: SistemaNovo):
self._sistema = sistema_novo
def requisicao_antiga(self, dados: dict) -> str:
return self._sistema.requisicao_nova(
nome=dados.get("nome", ""),
versao=dados.get("versao", 1)
)
Uso
sistema_antigo = SistemaAntigo()
sistema_novo = SistemaNovo()
adaptador = AdapterSistema(sistema_novo)
print(sistema_antigo.requisicao_antiga({"nome": "Python"}))
print(adaptador.requisicao_antiga({"nome": "Python", "versao": 3}))
O guia do Refactoring Guru sobre Adapter oferece diagramas e explicações visuais que ajudam a compreender melhor este padrão.
Decorator (Decorador)
Não confunda com os decoradores do Python! O padrão Decorator (estrutural) permite adicionar comportamentos a objetos de forma dinâmica, envolvendo-os em objetos "decoradores". O Python implementa esse padrão de forma nativa através dos decoradores da linguagem, que são açúcar sintático para funções que recebem e retornam funções.
import functools
import time
def log_execucao(func):
@functools.wraps(func)
def wrapper(*args, *kwargs):
print(f"Executando {func.name}...")
resultado = func(args, **kwargs)
print(f"Finalizado {func.name}")
return resultado
return wrapper
def medir_tempo(func):
@functools.wraps(func)
def wrapper(*args, *kwargs):
inicio = time.time()
resultado = func(args, **kwargs)
duracao = time.time() - inicio
print(f"{func.name} levou {duracao:.3f}s")
return resultado
return wrapper
@log_execucao
@medir_tempo
def processar_dados(arquivo: str) -> list:
time.sleep(0.5)
return [f"Dado de {arquivo}"]
resultado = processar_dados("clientes.csv")
A combinação de múltiplos decoradores em Python é uma aplicação direta do padrão Decorator, permitindo compor comportamentos de forma extremamente elegante e reutilizável.
Facade (Fachada)
O Facade fornece uma interface simplificada para um sistema complexo. É como o balcão de informações de um shopping: você não precisa saber os detalhes internos de cada loja — basta perguntar no balcão.
class Autenticacao:
def login(self, usuario: str, senha: str) -> bool:
print(f"Autenticando {usuario}...")
return True
class Pagamento:
def processar(self, valor: float) -> bool:
print(f"Processando pagamento de R${valor:.2f}...")
return True
class Envio:
def agendar(self, endereco: str) -> str:
codigo = f"ENV-{hash(endereco) % 10000:04d}"
print(f"Envio agendado para {endereco} — código {codigo}")
return codigo
class LojaFacade:
def init(self):
self._auth = Autenticacao()
self._pagamento = Pagamento()
self._envio = Envio()
def comprar_produto(self, usuario: str, senha: str,
valor: float, endereco: str) -> str:
if not self._auth.login(usuario, senha):
return "Falha na autenticação"
if not self._pagamento.processar(valor):
return "Falha no pagamento"
codigo = self._envio.agendar(endereco)
return f"Compra realizada! Código de envio: {codigo}"
Uso
loja = LojaFacade()
resultado = loja.comprar_produto(
"[email protected]", "123456", 149.90, "Rua A, 123"
)
print(resultado)
Padrões Comportamentais
Padrões comportamentais tratam da comunicação entre objetos, definindo como eles interagem e distribuem responsabilidades. Estes são os padrões que mais impactam a flexibilidade e a extensibilidade do código.
Strategy (Estratégia)
O Strategy define uma família de algoritmos intercambiáveis e permite que o algoritmo varie independentemente dos clientes que o utilizam. Em Python, você pode implementar Strategy de forma extremamente concisa usando funções.
from typing import Callable
Estratégias como funções — Python puro!
def calcular_frete_normal(peso: float) -> float:
return peso * 1.5 + 10
def calcular_frete_expresso(peso: float) -> float:
return peso * 3.0 + 20
def calcular_frete_internacional(peso: float) -> float:
return peso 5.0 + 50 + peso 0.1
class CalculadoraFrete:
def init(self, estrategia: Callable[[float], float]):
self._estrategia = estrategia
def definir_estrategia(self, estrategia: Callable[[float], float]):
self._estrategia = estrategia
def calcular(self, peso: float) -> float:
return self._estrategia(peso)
Uso
calculadora = CalculadoraFrete(calcular_frete_normal)
print(f"Frete normal: R${calculadora.calcular(5):.2f}")
calculadora.definir_estrategia(calcular_frete_expresso)
print(f"Frete expresso: R${calculadora.calcular(5):.2f}")
calculadora.definir_estrategia(calcular_frete_internacional)
print(f"Frete internacional: R${calculadora.calcular(5):.2f}")
Veja como Python simplifica o Strategy: em linguagens como Java ou C#, você precisaria de interfaces e classes separadas. Em Python, funções de primeira classe tornam a implementação trivial. O artigo da Real Python sobre Strategy Pattern aprofunda ainda mais este conceito.
Observer (Observador)
O Observer define uma dependência um-para-muitos entre objetos, de modo que quando um objeto muda de estado, todos os seus dependentes são notificados automaticamente. É o padrão por trás de sistemas de eventos, notificações e reatividade.
from abc import ABC, abstractmethod
class Observador(ABC):
@abstractmethod
def atualizar(self, evento: str, dados: dict) -> None:
pass
class Logger(Observador):
def atualizar(self, evento: str, dados: dict) -> None:
print(f"[LOG] Evento: {evento} | Dados: {dados}")
class EmailService(Observador):
def atualizar(self, evento: str, dados: dict) -> None:
if evento == "usuario_cadastrado":
print(f"[EMAIL] Boas-vindas para {dados.get('email')}")
class NotificacaoPush(Observador):
def atualizar(self, evento: str, dados: dict) -> None:
print(f"[PUSH] Notificação enviada para {dados.get('nome')}")
class EventManager:
def init(self):
self._observadores: list[Observador] = []
def inscrever(self, observador: Observador) -> None:
self._observadores.append(observador)
def cancelar(self, observador: Observador) -> None:
self._observadores.remove(observador)
def notificar(self, evento: str, dados: dict) -> None:
for obs in self._observadores:
obs.atualizar(evento, dados)
Uso
gerenciador = EventManager()
gerenciador.inscrever(Logger())
gerenciador.inscrever(EmailService())
gerenciador.inscrever(NotificacaoPush())
gerenciador.notificar("usuario_cadastrado", {
"nome": "Maria Silva",
"email": "[email protected]"
})
Frameworks modernos como Django utilizam o padrão Observer através do sistema de signals, permitindo que partes da aplicação reajam a eventos como salvamento de modelos ou login de usuários.
Command (Comando)
O Command transforma uma solicitação em um objeto independente que contém toda a informação necessária para executar a ação. Isso permite parametrizar métodos, fazer fila de operações e implementar desfazer/refazer.
from abc import ABC, abstractmethod
class Comando(ABC):
@abstractmethod
def executar(self) -> None:
pass
@abstractmethod
def desfazer(self) -> None:
pass
class ComandoLuz(Comando):
def init(self, comodo: str):
self._comodo = comodo
self._ligada = False
def executar(self) -> None:
self._ligada = True
print(f"Luz do {self._comodo} ligada")
def desfazer(self) -> None:
self._ligada = False
print(f"Luz do {self._comodo} desligada")
class ControleRemoto:
def init(self):
self._historico: list[Comando] = []
def executar(self, comando: Comando) -> None:
comando.executar()
self._historico.append(comando)
def desfazer(self) -> None:
if self._historico:
comando = self._historico.pop()
comando.desfazer()
Uso
luz_sala = ComandoLuz("sala")
luz_quarto = ComandoLuz("quarto")
controle = ControleRemoto()
controle.executar(luz_sala)
controle.executar(luz_quarto)
controle.desfazer() # Desfaz último comando
controle.desfazer() # Desfaz penúltimo
A GeeksforGeeks tem um tutorial detalhado sobre Command Pattern em Python com exemplos adicionais e casos de uso avançados.
Padrões de Projeto com Type Hints
Python moderno oferece suporte robusto a type hints, que melhoram significativamente a legibilidade e a segurança dos design patterns. Usar type hints permite que IDEs e ferramentas de análise estática como mypy detectem erros antes da execução.
Se você ainda não domina type hints, confira nosso guia completo sobre Type Hints em Python antes de aplicar os padrões abaixo.
from typing import Protocol, runtime_checkable
@runtime_checkable
class Serializavel(Protocol):
def serializar(self) -> str: ...
class JsonSerializer:
def serializar(self) -> str:
return '{"formato": "JSON"}'
class XmlSerializer:
def serializar(self) -> str:
return "XML "
def exportar(serializador: Serializavel) -> None:
if isinstance(serializador, Serializavel):
print(serializador.serializar())
exportar(JsonSerializer())
exportar(XmlSerializer())
O uso de Protocol (introduzido no PEP 544) permite tipagem estrutural, onde o que importa é a estrutura do objeto e não sua herança formal — um conceito que se alinha perfeitamente com o duck typing do Python.
Quando (Não) Usar Design Patterns
Design patterns são ferramentas poderosas, mas não devem ser aplicados indiscriminadamente. Aqui estão algumas diretrizes práticas:
- Use patterns quando você identifica um problema recorrente que já tem uma solução consagrada
- Não force patterns — código simples e direto é quase sempre melhor do que código superengenhado com patterns desnecessários
- Prefira soluções nativas do Python — muitas vezes a própria linguagem já oferece abstrações que substituem patterns tradicionais (ex: decoradores, geradores, context managers)
- Considere a equipe — patterns só são úteis se todos no time os compreendem e concordam com seu uso
O guia do SourceMaking sobre Design Patterns oferece uma visão complementar com exemplos em múltiplas linguagens e discussões sobre quando cada padrão é apropriado.
Conclusão
Design patterns são parte fundamental do repertório de qualquer desenvolvedor Python que busca escrever código profissional, manutenível e escalável. Neste guia, você aprendeu os padrões mais importantes organizados por categoria:
- Criacionais: Singleton, Factory Method e Builder
- Estruturais: Adapter, Decorator e Facade
- Comportamentais: Strategy, Observer e Command
Cada padrão resolve um problema específico e, quando bem aplicado, torna seu código mais flexível, reutilizável e fácil de entender. Lembre-se: o objetivo não é decorar todos os patterns, mas sim construir um vocabulário de soluções que você possa aplicar quando o problema certo aparecer.
Para continuar seus estudos, explore o repositório python-patterns no GitHub, leia a documentação oficial sobre descritores do Python (usados em vários patterns avançados) e pratique refatorando código existente para identificar oportunidades de aplicar os padrões que aprendeu aqui.
Domine os patterns, mas nunca se esqueça: o padrão mais importante é o bom senso combinado com o conhecimento profundo da linguagem Python.