<h2>Design Patterns em Python: Factory, Repository, Observer e Strategy</h2>
<p>Design patterns são soluções comprovadas para problemas recorrentes no desenvolvimento de software. Eles não são códigos prontos para copiar, mas sim templates de como estruturar seu código para resolver desafios específicos de forma elegante e manutenível. Neste artigo, exploraremos quatro padrões essenciais que todo desenvolvedor Python deve compreender: Factory (criação de objetos), Repository (acesso a dados), Observer (comunicação entre objetos) e Strategy (algoritmos intercambiáveis).</p>
<p>A importância desses padrões vai além da teoria acadêmica. Quando você trabalha em projetos reais, projetos grandes com múltiplos desenvolvedores, eventualmente terá que lidar com código complexo que precisa ser fácil de testar, estender e manter. Design patterns fornecem um vocabulário comum que permite comunicação clara entre desenvolvedores e reduz a necessidade de explicações detalhadas sobre a arquitetura do código.</p>
<h2>Factory Pattern: Criando Objetos de Forma Inteligente</h2>
<h3>O Conceito Fundamental</h3>
<p>O padrão Factory resolve um problema simples mas fundamental: como criar objetos sem acoplamento direto à classe concreta? Em vez de usar <code>new</code> ou o construtor diretamente em diversos lugares do código, você delega a criação para uma classe ou método especializado. Isso permite que você mude a implementação sem alterar o código que utiliza o objeto.</p>
<p>Imagine um sistema que trabalha com diferentes tipos de pagamento: cartão de crédito, boleto, PIX. Se você espalhar <code>if/elif</code> para instanciar cada um por todo o código, uma simples mudança se torna um pesadelo. Factory centraliza isso em um único lugar.</p>
<h3>Implementação Prática com Factory Method</h3>
<pre><code class="language-python">from abc import ABC, abstractmethod
Classes abstratas que definem o contrato
class Pagamento(ABC):
@abstractmethod
def processar(self, valor: float) -> bool:
pass
class CartaoCredito(Pagamento):
def processar(self, valor: float) -> bool:
print(f"Processando R$ {valor} no cartão de crédito")
return True
class Boleto(Pagamento):
def processar(self, valor: float) -> bool:
print(f"Gerando boleto de R$ {valor}")
return True
class PIX(Pagamento):
def processar(self, valor: float) -> bool:
print(f"Enviando PIX de R$ {valor}")
return True
Factory Method
class PagamentoFactory:
_tipos = {
'cartao': CartaoCredito,
'boleto': Boleto,
'pix': PIX
}
@staticmethod
def criar(tipo: str) -> Pagamento:
if tipo not in PagamentoFactory._tipos:
raise ValueError(f"Tipo de pagamento '{tipo}' não suportado")
return PagamentoFactory._tipos[tipo]()
Uso
pagamento = PagamentoFactory.criar('pix')
pagamento.processar(150.00)
pagamento = PagamentoFactory.criar('cartao')
pagamento.processar(500.00)</code></pre>
<p>Observe que o código cliente não conhece as classes concretas. Se você precisar adicionar um novo método de pagamento (como Google Pay), basta criar a classe e registrá-la no dicionário <code>_tipos</code>. Não há necessidade de alterar o código que usa a factory.</p>
<h3>Variação com Abstract Factory</h3>
<p>Para cenários mais complexos onde você precisa criar famílias de objetos relacionados, use Abstract Factory:</p>
<pre><code class="language-python">from abc import ABC, abstractmethod
class BotaoUI(ABC):
@abstractmethod
def renderizar(self) -> str:
pass
class CampoUI(ABC):
@abstractmethod
def renderizar(self) -> str:
pass
class BotaoWindows(BotaoUI):
def renderizar(self) -> str:
return "Botão com estilo Windows"
class BotaoMac(BotaoUI):
def renderizar(self) -> str:
return "Botão com estilo Mac"
class CampoWindows(CampoUI):
def renderizar(self) -> str:
return "Campo com aparência Windows"
class CampoMac(CampoUI):
def renderizar(self) -> str:
return "Campo com aparência Mac"
class FactoryUI(ABC):
@abstractmethod
def criar_botao(self) -> BotaoUI:
pass
@abstractmethod
def criar_campo(self) -> CampoUI:
pass
class FactoryWindows(FactoryUI):
def criar_botao(self) -> BotaoUI:
return BotaoWindows()
def criar_campo(self) -> CampoUI:
return CampoWindows()
class FactoryMac(FactoryUI):
def criar_botao(self) -> BotaoUI:
return BotaoMac()
def criar_campo(self) -> CampoUI:
return CampoMac()
Uso
def criar_interface(sistema_operacional: str):
if sistema_operacional == 'windows':
factory = FactoryWindows()
else:
factory = FactoryMac()
botao = factory.criar_botao()
campo = factory.criar_campo()
print(botao.renderizar())
print(campo.renderizar())
criar_interface('windows')</code></pre>
<p>A Abstract Factory garante que você sempre cria componentes relacionados (nunca mistura um botão Windows com um campo Mac).</p>
<h2>Repository Pattern: Abstraindo o Acesso a Dados</h2>
<h3>Por Que Abstrair Dados?</h3>
<p>O padrão Repository funciona como um intermediário entre a lógica de negócio e a camada de dados. Em vez de espalhar queries SQL ou chamadas a bancos de dados por todo seu código, você centraliza isso. O grande benefício? Você consegue trocar de banco de dados ou implementação sem mexer na lógica de negócio. Também fica trivial fazer testes sem um banco de dados real.</p>
<h3>Repository Genérico com Python</h3>
<pre><code class="language-python">from abc import ABC, abstractmethod
from typing import List, Optional, TypeVar, Generic
import json
import os
T = TypeVar('T')
Definição abstrata do repositório
class Repository(ABC, Generic[T]):
@abstractmethod
def adicionar(self, entidade: T) -> None:
pass
@abstractmethod
def obter_por_id(self, id: int) -> Optional[T]:
pass
@abstractmethod
def obter_todos(self) -> List[T]:
pass
@abstractmethod
def atualizar(self, entidade: T) -> None:
pass
@abstractmethod
def deletar(self, id: int) -> None:
pass
Entidade de domínio
class Usuario:
def __init__(self, id: int, nome: str, email: str):
self.id = id
self.nome = nome
self.email = email
def __repr__(self):
return f"Usuario(id={self.id}, nome='{self.nome}', email='{self.email}')"
Implementação em memória (perfeita para testes)
class RepositorioUsuarioEmMemoria(Repository[Usuario]):
def __init__(self):
self._usuarios = {}
def adicionar(self, usuario: Usuario) -> None:
self._usuarios[usuario.id] = usuario
def obter_por_id(self, id: int) -> Optional[Usuario]:
return self._usuarios.get(id)
def obter_todos(self) -> List[Usuario]:
return list(self._usuarios.values())
def atualizar(self, usuario: Usuario) -> None:
if usuario.id in self._usuarios:
self._usuarios[usuario.id] = usuario
def deletar(self, id: int) -> None:
if id in self._usuarios:
del self._usuarios[id]
Implementação em JSON (simulando persistência)
class RepositorioUsuarioJSON(Repository[Usuario]):
def __init__(self, arquivo: str = "usuarios.json"):
self.arquivo = arquivo
self._garantir_arquivo()
def _garantir_arquivo(self):
if not os.path.exists(self.arquivo):
with open(self.arquivo, 'w') as f:
json.dump({}, f)
def _carregar(self) -> dict:
with open(self.arquivo, 'r') as f:
return json.load(f)
def _salvar(self, dados: dict):
with open(self.arquivo, 'w') as f:
json.dump(dados, f, indent=2)
def adicionar(self, usuario: Usuario) -> None:
dados = self._carregar()
dados[str(usuario.id)] = {
'id': usuario.id,
'nome': usuario.nome,
'email': usuario.email
}
self._salvar(dados)
def obter_por_id(self, id: int) -> Optional[Usuario]:
dados = self._carregar()
if str(id) in dados:
u = dados[str(id)]
return Usuario(u['id'], u['nome'], u['email'])
return None
def obter_todos(self) -> List[Usuario]:
dados = self._carregar()
return [Usuario(u['id'], u['nome'], u['email']) for u in dados.values()]
def atualizar(self, usuario: Usuario) -> None:
dados = self._carregar()
dados[str(usuario.id)] = {
'id': usuario.id,
'nome': usuario.nome,
'email': usuario.email
}
self._salvar(dados)
def deletar(self, id: int) -> None:
dados = self._carregar()
if str(id) in dados:
del dados[str(id)]
self._salvar(dados)
Serviço de negócio que usa o repositório
class ServicoUsuario:
def __init__(self, repositorio: Repository[Usuario]):
self.repositorio = repositorio
def registrar_usuario(self, id: int, nome: str, email: str) -> Usuario:
usuario = Usuario(id, nome, email)
self.repositorio.adicionar(usuario)
return usuario
def obter_usuario(self, id: int) -> Optional[Usuario]:
return self.repositorio.obter_por_id(id)
def listar_usuarios(self) -> List[Usuario]:
return self.repositorio.obter_todos()
Uso
if __name__ == "__main__":
Teste com repositório em memória
repo_memoria = RepositorioUsuarioEmMemoria()
servico = ServicoUsuario(repo_memoria)
servico.registrar_usuario(1, "João Silva", "joao@example.com")
servico.registrar_usuario(2, "Maria Santos", "maria@example.com")
print("Usuários em memória:", servico.listar_usuarios())
Se precisar trocar para JSON, apenas uma linha muda:
repo_json = RepositorioUsuarioJSON()
servico = ServicoUsuario(repo_json)
servico.registrar_usuario(3, "Pedro Costa", "pedro@example.com")
print("Usuários em JSON:", servico.listar_usuarios())</code></pre>
<p>Veja como a classe <code>ServicoUsuario</code> não precisa saber se os dados vêm de um banco de dados real, JSON ou até uma API. Ela trabalha com a abstração <code>Repository</code>, permitindo que você mude a implementação conforme necessário, inclusive para testes.</p>
<h2>Observer Pattern: Comunicação Reativa Entre Objetos</h2>
<h3>Entendendo o Fluxo de Eventos</h3>
<p>O padrão Observer é perfeito para cenários onde múltiplos objetos precisam reagir a mudanças em outro objeto. Em vez de polling (perguntar constantemente "mudou?"), o Observer implementa push: quando algo muda, notifica interessados automaticamente. É amplamente usado em interfaces gráficas, sistemas de eventos e arquiteturas reativas.</p>
<h3>Implementação Clássica</h3>
<pre><code class="language-python">from abc import ABC, abstractmethod
from typing import List
Interface para observadores
class Observador(ABC):
@abstractmethod
def atualizar(self, evento: str, dados: dict) -> None:
pass
Sujeito (observable)
class Conta:
def __init__(self, saldo_inicial: float = 0):
self._saldo = saldo_inicial
self._observadores: List[Observador] = []
def anexar(self, observador: Observador) -> None:
if observador not in self._observadores:
self._observadores.append(observador)
def desanexar(self, observador: Observador) -> None:
if observador in self._observadores:
self._observadores.remove(observador)
def _notificar(self, evento: str, dados: dict) -> None:
for observador in self._observadores:
observador.atualizar(evento, dados)
def depositar(self, valor: float) -> None:
self._saldo += valor
self._notificar('deposito', {
'valor': valor,
'saldo_novo': self._saldo
})
def sacar(self, valor: float) -> None:
if valor <= self._saldo:
self._saldo -= valor
self._notificar('saque', {
'valor': valor,
'saldo_novo': self._saldo
})
else:
self._notificar('saque_rejeitado', {
'valor': valor,
'saldo_disponivel': self._saldo
})
@property
def saldo(self) -> float:
return self._saldo
Observadores concretos
class LogTransacao(Observador):
def atualizar(self, evento: str, dados: dict) -> None:
print(f"[LOG] Evento: {evento} | Dados: {dados}")
class NotificacaoEmail(Observador):
def atualizar(self, evento: str, dados: dict) -> None:
if evento in ['deposito', 'saque']:
print(f"[EMAIL] Transação realizada: {evento}")
print(f"[EMAIL] Novo saldo: R$ {dados.get('saldo_novo')}")
class AlertaSaldoBaixo(Observador):
def __init__(self, limite: float = 100):
self.limite = limite
def atualizar(self, evento: str, dados: dict) -> None:
saldo = dados.get('saldo_novo')
if saldo is not None and saldo < self.limite:
print(f"[ALERTA] Saldo baixo! R$ {saldo} < R$ {self.limite}")
Uso
conta = Conta(1000)
log = LogTransacao()
email = NotificacaoEmail()
alerta = AlertaSaldoBaixo(200)
conta.anexar(log)
conta.anexar(email)
conta.anexar(alerta)
print("=== Depósito ===")
conta.depositar(500)
print("\n=== Saque pequeno ===")
conta.sacar(100)
print("\n=== Saque grande ===")
conta.sacar(1200)
print(f"\nSaldo final: R$ {conta.saldo}")</code></pre>
<p>Note que adicionar novos observadores não afeta a classe <code>Conta</code>. Se você precisar de um SMS de alerta ou sincronizar com um sistema externo, basta criar um novo observador. Essa desacoplagem é o poder real do padrão.</p>
<h3>Observer com Decoradores (Pythônico)</h3>
<p>Python permite uma abordagem mais elegante usando decoradores:</p>
<pre><code class="language-python">from functools import wraps
from typing import Callable, List, Dict, Any
class EventManager:
def __init__(self):
self._listeners: Dict[str, List[Callable]] = {}
def on(self, evento: str):
"""Decorador para registrar handlers de eventos"""
def decorador(func: Callable) -> Callable:
if evento not in self._listeners:
self._listeners[evento] = []
self._listeners[evento].append(func)
return func
return decorador
def emit(self, evento: str, **dados: Any) -> None:
"""Dispara um evento para todos os listeners"""
if evento in self._listeners:
for handler in self._listeners[evento]:
handler(**dados)
Uso com decoradores
gerenciador = EventManager()
@gerenciador.on('usuario_criado')
def enviar_email_boas_vindas(usuario_id: int, email: str, **kwargs):
print(f"[EMAIL] Bem-vindo {email}!")
@gerenciador.on('usuario_criado')
def registrar_audit(usuario_id: int, **kwargs):
print(f"[AUDIT] Novo usuário criado: {usuario_id}")
Dispara o evento
gerenciador.emit('usuario_criado', usuario_id=1, email='novo@example.com')</code></pre>
<h2>Strategy Pattern: Intercambiando Algoritmos em Tempo de Execução</h2>
<h3>O Problema de Múltiplos Caminhos</h3>
<p>Frequentemente, você precisa executar diferentes algoritmos baseado em condições. A abordagem ingênua é encher seu código com <code>if/elif/else</code>. O Strategy pattern diz: encapsule cada algoritmo em uma classe separada e deixe o cliente escolher qual usar. Quando você precisa de um novo algoritmo, não modifica o existente; você apenas adiciona um novo.</p>
<h3>Implementação Prática com Cálculo de Preços</h3>
<pre><code class="language-python">from abc import ABC, abstractmethod
from datetime import datetime
Interface para estratégias de desconto
class EstrategiaDesconto(ABC):
@abstractmethod
def calcular_desconto(self, valor: float) -> float:
pass
class SemDesconto(EstrategiaDesconto):
def calcular_desconto(self, valor: float) -> float:
return 0
class DescontoFixo(EstrategiaDesconto):
def __init__(self, percentual: float):
self.percentual = percentual
def calcular_desconto(self, valor: float) -> float:
return valor * (self.percentual / 100)
class DescontoProgressivo(EstrategiaDesconto):
"""Quanto maior a compra, maior o desconto"""
def calcular_desconto(self, valor: float) -> float:
if valor > 1000:
return valor * 0.20
elif valor > 500:
return valor * 0.10
elif valor > 100:
return valor * 0.05
return 0
class DescontoParaCliente(EstrategiaDesconto):
"""Black Friday ou oferta especial"""
def calcular_desconto(self, valor: float) -> float:
Simula ofertas em horários específicos
hora_atual = datetime.now().hour
if 20 <= hora_atual <= 23: # Oferta noturna
return valor * 0.15
return 0
Contexto que usa a estratégia
class Pedido:
def __init__(self, items: list, estrategia_desconto: EstrategiaDesconto = None):
self.items = items
self.estrategia = estrategia_desconto or SemDesconto()
def mudar_estrategia(self, estrategia: EstrategiaDesconto) -> None:
self.estrategia = estrategia
def calcular_total(self) -> dict:
subtotal = sum(item['preco'] * item['quantidade'] for item in self.items)
desconto = self.estrategia.calcular_desconto(subtotal)
total = subtotal - desconto
return {
'subtotal': subtotal,
'desconto': desconto,
'total': total,
'estrategia': self.estrategia.__class__.__name__
}
Uso
items = [
{'preco': 100, 'quantidade': 2},
{'preco': 150, 'quantidade': 3}
]
Cliente normal, sem desconto
pedido = Pedido(items)
print("Sem desconto:", pedido.calcular_total())
Cliente especial com desconto fixo
pedido.mudar_estrategia(DescontoFixo(10))
print("Com 10% fixo:", pedido.calcular_total())
Cliente que fez uma grande compra
pedido.mudar_estrategia(DescontoProgressivo())
print("Com desconto progressivo:", pedido.calcular_total())
Black Friday
pedido.mudar_estrategia(DescontoParaCliente())
print("Black Friday:", pedido.calcular_total())</code></pre>
<h3>Strategy com Funções (Mais Simples)</h3>
<p>Para casos mais simples, Python permite usar funções diretamente como estratégias:</p>
<pre><code class="language-python">from typing import Callable
class ProcessadorPagamento:
def __init__(self, estrategia_taxa: Callable[[float], float]):
self.estrategia_taxa = estrategia_taxa
def processar(self, valor: float) -> dict:
taxa = self.estrategia_taxa(valor)
total = valor + taxa
return {
'valor_original': valor,
'taxa': taxa,
'total_cobrado': total
}
Estratégias como funções simples
def taxa_cartao_credito(valor: float) -> float:
return valor * 0.03
def taxa_boleto(valor: float) -> float:
return 3.50 # Taxa fixa
def taxa_pix(valor: float) -> float:
return 0 # PIX sem taxa
Uso
processador = ProcessadorPagamento(taxa_pix)
print(processador.processar(100))
processador = ProcessadorPagamento(taxa_cartao_credito)
print(processador.processar(100))
processador = ProcessadorPagamento(taxa_boleto)
print(processador.processar(100))</code></pre>
<p>Essa abordagem funcional é perfeita quando suas estratégias são simples. Para lógica mais complexa, use classes.</p>
<h2>Conclusão</h2>
<p>Dominar esses quatro padrões transforma você em um desenvolvedor que escreve código profissional e escalável. <strong>Factory</strong> elimina o acoplamento à criação de objetos, permitindo que você adicione novos tipos sem modificar código existente. <strong>Repository</strong> abstrai a complexidade de acesso a dados, facilitando testes e mudanças de implementação. <strong>Observer</strong> desacopla componentes através de comunicação baseada em eventos, essencial para sistemas reativos. <strong>Strategy</strong> encapsula algoritmos intercambiáveis, tornando seu código extensível sem necessidade de modificações constantes.</p>
<p>O diferencial de um bom desenvolvedor não é memorizar padrões, mas reconhecer quando cada um resolve genuinamente um problema no código real. Use-os quando agregarem valor, não por usar. Python, sendo uma linguagem expressiva, frequentemente permite soluções elegantes que aproveitam esses padrões sem cerimônia excessiva.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Design_Patterns" target="_blank" rel="noopener noreferrer">Design Patterns: Elements of Reusable Object-Oriented Software - Gang of Four</a></li>
<li><a href="https://realpython.com/design-patterns-python/" target="_blank" rel="noopener noreferrer">Real Python - Design Patterns in Python</a></li>
<li><a href="https://refactoring.guru/design-patterns" target="_blank" rel="noopener noreferrer">Refactoring Guru - Design Patterns</a></li>
<li><a href="https://docs.python.org/3/library/abc.html" target="_blank" rel="noopener noreferrer">Python Official Documentation - abc module</a></li>
<li><a href="https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html" target="_blank" rel="noopener noreferrer">Clean Architecture by Robert C. Martin</a></li>
</ul>
<p><!-- FIM --></p>