Chega de Decorar Padrões de Projeto! Entenda a Lógica da Multiplicação (com Python e FastAPI)
Introdução: O Dilema do 2x3
Você sabe que 2x3 é 6. Mas não porque decorou. Você sabe que é 6 porque entendeu que 2x3 é a mesma coisa que 2+2+2, ou 3+3. Se você esquecer a tabuada, consegue deduzir o resultado sozinho.
Agora, me responda: Por que com Padrões de Projeto é diferente?
Se eu perguntar a diferença entre Strategy e Adapter, muitos desenvolvedores travam. A sensação é que só existe decoreba: você usa o que viu no último projeto do seu sênior ou chuta baseado na "vivência".
Pois eu vou te mostrar que Padrões de Projeto também têm uma lógica matemática por trás. Depois de ler este artigo, você não vai mais precisar decorar. Você vai derivar qualquer padrão sozinho, exatamente como você deriva a multiplicação.
A "Lei Universal" por trás de TODOS os Padrões
Se existe uma única regra na engenharia de software que rege os 23 padrões do GOF (Gang of Four), ela é esta:
"Todo acoplamento direto (hardcoding) entre duas classes é uma dívida que impede a mudança."
Traduzindo para o português: Se a Classe A dá um new na Classe B (ou chama um método estático dela), você colou A e B com supercola. Se amanhã precisar de uma Classe C no lugar de B, você terá que quebrar a Classe A para fazer essa troca.
Para resolver isso, a engenharia de software criou uma fórmula universal de desacoplamento:
A depende de uma Interface (I). B e C implementam a Interface (I). Um "Injetor" (Factory ou DI) decide qual deles (B ou C) entra em A.
Pois bem. Todos os 23 padrões de projeto clássicos são apenas VARIAÇÕES dessa fórmula. O nome do padrão muda dependendo de O QUE você está colocando dentro dessa Interface. É pura álgebra.
Padrões vs. SOLID: A Física e a Engenharia
Aqui está o pulo do gato que ninguém te conta:
- SOLID é a Física Quântica. É o porquê.
- Padrões de Projeto são a Engenharia Civil. É o como.
Os padrões existem para aplicar as regras do SOLID na prática:
- S (Single Responsibility): Um padrão só resolve UM tipo de variação.
- O (Open/Closed): É a alma de todo padrão! Eles existem para você adicionar comportamentos novos (aberto para extensão) sem editar as classes existentes (fechado para modificação).
- L (Liskov Substitution): As implementações concretas devem poder ser trocadas sem quebrar a interface.
- I (Interface Segregation): Padrões como
Adaptercriam interfaces mais limpas. - D (Dependency Inversion): A fórmula universal que vimos acima: depende da abstração, não do concreto.
Resumo: Se você entendeu o SOLID, você já sabe o motivo de existirem padrões. Agora, vamos ao mapa de como escolher o certo.
O Mapa do Tesouro (Por que existem 23?)
Se a lógica é a mesma, por que 23 padrões? Porque o problema varia em 3 dimensões diferentes. Você não precisa decorar os 23. Você só precisa saber em qual galho da árvore seu problema está.
1. Padrões Criacionais (5) - Foco no "S" e "O"
O problema: Fazer new Objeto() diretamente viola o princípio de que a classe não deveria saber como criar suas dependências.
A lógica: "Delegue a criação da abstração para outra entidade."
Exemplos: Factory, Abstract Factory, Builder.
2. Padrões Estruturais (7) - Foco no "L" e "I"
O problema: Como juntar objetos e classes para formar estruturas maiores, sem colá-los e sem usar herança excessiva?
A lógica: "Use composição (ter um objeto dentro de outro) em vez de herança para estender funcionalidades."
Exemplos: Adapter, Decorator, Facade, Proxy.
3. Padrões Comportamentais (11) - Foco no "D" e "O"
O problema: Como objetos diferentes trocam informações sem saber os detalhes internos uns dos outros?
A lógica: "Desacople o remetente do destinatário ou do algoritmo."
Exemplos: Strategy, Observer, Command, State.
Como tomar a decisão (sem chutar)
Quando você for codar, não pense nos 23. Faça apenas 3 perguntas, em ordem:
- O que está variando? É o OBJETO em si (como ele é criado)? → Vá para Criacionais (Factory/Builder).
- O que está variando? É a ESTRUTURA (como os objetos se conectam)? → Vá para Estruturais (Adapter/Decorator).
- O que está variando? É o COMPORTAMENTO (como eles agem)? → Vá para Comportamentais (Strategy/Observer).
Simples assim. Agora, vamos ver isso na prática com um exemplo real em Python + FastAPI.
Mão na Massa: Sistema de Pagamentos (SEM Padrão vs. COM Padrão)
Imagine que precisamos de um endpoint POST /pay. Temos 3 métodos: PIX (5% desconto), Cartão (1% cashback) e Boleto (vencimento em 3 dias).
O Código SEM Padrão (O "If/Else" Monstro)
Aqui está o código que viola o SOLID e te deixa refém de editar o arquivo principal toda vez que um novo método chegar.
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
router = APIRouter()
class PaymentRequest(BaseModel):
order_id: int
method: str # "pix", "credit", "boleto"
class PaymentResponse(BaseModel):
order_id: int
final_price: float
message: str
@router.post("/pay", response_model=PaymentResponse)
async def process_payment(request: PaymentRequest):
order_price = 100.00 # Simulando busca no banco
# LÓGICA COLADA AQUI (O PECADO)
if request.method == "pix":
discount = order_price * 0.05
final_price = order_price - discount
message = f"PIX aprovado! Desconto de R${discount:.2f}"
elif request.method == "credit":
cashback = order_price * 0.01
final_price = order_price
message = f"Cartão aprovado! Cashback de R${cashback:.2f}"
elif request.method == "boleto":
final_price = order_price
message = "Boleto gerado! Vence em 3 dias úteis."
else:
raise HTTPException(status_code=400, detail="Método inválido")
return PaymentResponse(order_id=request.order_id, final_price=final_price, message=message)
A dor: Se o chefe pedir "PIX Parcelado", você vai abrir esse arquivo, mexer no if/elif e torcer para não quebrar o Boleto. Violou o OCP (Aberto/Fechado).
O Código COM Padrão (Strategy + Factory)
Vamos aplicar a lógica universal: isolar a regra que varia (cálculo) atrás de uma Interface.
1. Criamos a Interface (ABC)
# app/services/payment_strategy.py
from abc import ABC, abstractmethod
from pydantic import BaseModel
class PaymentResult(BaseModel):
final_price: float
message: str
class PaymentProcessor(ABC):
@abstractmethod
def process(self, order_price: float) -> PaymentResult:
pass
2. Criamos as implementações (as "Estratégias")
# app/services/payment_strategy.py
class PixProcessor(PaymentProcessor):
def process(self, order_price: float) -> PaymentResult:
discount = order_price * 0.05
return PaymentResult(
final_price=order_price - discount,
message=f"PIX aprovado! Desconto de R${discount:.2f}"
)
class CreditProcessor(PaymentProcessor):
def process(self, order_price: float) -> PaymentResult:
cashback = order_price * 0.01
return PaymentResult(
final_price=order_price,
message=f"Cartão aprovado! Cashback de R${cashback:.2f}"
)
class BoletoProcessor(PaymentProcessor):
def process(self, order_price: float) -> PaymentResult:
return PaymentResult(
final_price=order_price,
message="Boleto gerado! Vence em 3 dias úteis."
)
3. Criamos a Fábrica (Factory) para escolher
# app/services/payment_strategy.py
# Mapeamento (Dicionário) - Aberto para extensão!
PROCESSORS = {
"pix": PixProcessor(),
"credit": CreditProcessor(),
"boleto": BoletoProcessor(),
}
def get_payment_processor(method: str) -> PaymentProcessor:
processor = PROCESSORS.get(method)
if not processor:
raise ValueError(f"Método '{method}' não suportado.")
return processor
4. O Endpoint MAGRO no FastAPI
Agora a rota fica burra, limpa e linda. Ela só orquestra.
# app/api/routes/payments.py (COM PADRÃO)
from fastapi import APIRouter, HTTPException
from app.services.payment_strategy import get_payment_processor, PaymentRequest, PaymentResponse
router = APIRouter()
@router.post("/pay", response_model=PaymentResponse)
async def process_payment(request: PaymentRequest):
try:
# 1. A Factory escolhe a estratégia
processor = get_payment_processor(request.method)
# 2. Executa a lógica sem if/else
order_price = 100.00
result = processor.process(order_price)
return PaymentResponse(
order_id=request.order_id,
final_price=result.final_price,
message=result.message
)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
A Comparação (Entendendo a Lógica)
| Situação | SEM Padrão (If/Else) | COM Padrão (Strategy) |
|---|---|---|
| Adicionar "Cripto" | Você edita a rota, mexe no if/elif, reza para não errar a indentação. | Você cria CryptoProcessor, adiciona "crypto": CryptoProcessor() no dicionário. A rota original nem é tocada! |
| Testar o PIX | Precisa mockar a requisição HTTP inteira. | Você instancia PixProcessor() no teste unitário e testa ele isolado. |
| Manutenção | Arquivo da rota vira uma zona com 200+ linhas. | Rota tem 15 linhas. Cada regra tem seu próprio arquivo. |
Conclusão: A Árvore do Conhecimento
- Raiz (SOLID): Dependa de abstrações. Não acople diretamente.
- Tronco (Lógica Universal): Isole a variação atrás de uma Interface.
- Galhos (Criacional, Estrutural, Comportamental): O que está variando? (Criação, Formato ou Ação).
- Folhas (os 23 padrões): O cenário específico.
Você não precisa decorar as folhas. Você precisa conhecer o tronco. Quando você estiver codando e pensar "Poxa, se a regra de cálculo mudar, vou ter que editar a classe toda", você instantaneamente sabe que precisa de um padrão Comportamental. Daí você olha o cenário: "A regra é escolhida pelo cliente?" -> Strategy.
Agora pare de decorar e comece a deduzir. A matemática dos padrões é muito mais simples do que parece.
Gostou do conteúdo? Deixe seu comentário abaixo contando qual foi a maior "decoreba" que você já teve que fazer e como a lógica te ajudou a resolver!