Python

Artigo 47 — Design Patterns em Python Já leu

19 min de leitura

Artigo 47 — Design Patterns em Python
Design Patterns — padrões de projeto — são soluções reutilizáveis para problemas recorrentes no desenvolvimento de software. Foram catalogados pelo famoso

Artigo 47 — Design Patterns em Python

Prof. Ricardo Matos Módulo 8 · Testes, Qualidade e Boas Práticas · Artigo 47 de 52


Introdução

Design Patterns — padrões de projeto — são soluções reutilizáveis para problemas recorrentes no desenvolvimento de software. Foram catalogados pelo famoso livro do "Gang of Four" em 1994 e continuam relevantes. Em Python, muitos padrões são mais simples de implementar do que em Java ou C++ graças à tipagem dinâmica, funções de primeira classe e recursos como decoradores e metaclasses. Neste artigo cobrimos os padrões mais úteis no dia a dia, com implementações pythônicas.


Categorias de Padrões

Criacionais          Estruturais          Comportamentais
─────────────────    ─────────────────    ─────────────────
Singleton            Adapter              Strategy
Factory Method       Decorator            Observer
Abstract Factory     Facade               Command
Builder              Composite            Template Method
Prototype            Proxy                Iterator
                                          Chain of Responsibility
                                          State

Padrões Criacionais

Singleton

Garante que uma classe tenha apenas uma instância:

from threading import Lock
from typing import ClassVar


class Singleton:
    """Singleton thread-safe."""
    _instancia: ClassVar["Singleton | None"] = None
    _lock: ClassVar[Lock] = Lock()

    def __new__(cls):
        if cls._instancia is None:
            with cls._lock:
                if cls._instancia is None:   # double-checked locking
                    cls._instancia = super().__new__(cls)
        return cls._instancia


# Versão com decorator — mais Pythônica
def singleton(cls):
    instancias = {}
    def obter_instancia(*args, **kwargs):
        if cls not in instancias:
            instancias[cls] = cls(*args, **kwargs)
        return instancias[cls]
    return obter_instancia


@singleton
class ConfiguracaoApp:
    def __init__(self):
        self.debug    = False
        self.versao   = "1.0.0"
        self.database = "postgresql://localhost/escola"


c1 = ConfiguracaoApp()
c2 = ConfiguracaoApp()
print(c1 is c2)   # True — mesma instância

Factory Method

Define uma interface para criar objetos, deixando subclasses decidirem qual classe instanciar:

from abc import ABC, abstractmethod
from dataclasses import dataclass


@dataclass
class Relatorio:
    titulo:   str
    conteudo: str

    def exibir(self):
        print(f"[{self.__class__.__name__}] {self.titulo}")


class RelatorioPDF(Relatorio):
    def exportar(self) -> bytes:
        return f"PDF: {self.conteudo}".encode()


class RelatorioExcel(Relatorio):
    def exportar(self) -> bytes:
        return f"XLSX: {self.conteudo}".encode()


class RelatorioHTML(Relatorio):
    def exportar(self) -> str:
        return f"<html><body>{self.conteudo}</body></html>"


class FabricaRelatorios:
    """Factory simples baseada em dicionário."""

    _tipos = {
        "pdf":   RelatorioPDF,
        "excel": RelatorioExcel,
        "html":  RelatorioHTML,
    }

    @classmethod
    def criar(cls, tipo: str, titulo: str, conteudo: str) -> Relatorio:
        classe = cls._tipos.get(tipo.lower())
        if not classe:
            raise ValueError(f"Tipo desconhecido: {tipo}. "
                             f"Opções: {list(cls._tipos)}")
        return classe(titulo=titulo, conteudo=conteudo)

    @classmethod
    def registrar(cls, tipo: str, classe):
        """Extensível sem modificar a factory."""
        cls._tipos[tipo] = classe


# Uso
rel = FabricaRelatorios.criar("pdf", "Boletim Março", "Conteúdo...")
rel.exibir()

# Registrando novo tipo sem modificar a classe
class RelatorioCSV(Relatorio):
    pass

FabricaRelatorios.registrar("csv", RelatorioCSV)
rel_csv = FabricaRelatorios.criar("csv", "Dados", "a,b,c")

Builder

Constrói objetos complexos passo a passo:

from dataclasses import dataclass, field
from typing import Optional


@dataclass
class Consulta:
    tabela:    str
    campos:    list[str]    = field(default_factory=lambda: ["*"])
    condicoes: list[str]    = field(default_factory=list)
    ordenacao: Optional[str] = None
    limite:    Optional[int] = None
    offset:    int           = 0

    def to_sql(self) -> str:
        campos_str = ", ".join(self.campos)
        sql        = f"SELECT {campos_str} FROM {self.tabela}"
        if self.condicoes:
            sql += " WHERE " + " AND ".join(self.condicoes)
        if self.ordenacao:
            sql += f" ORDER BY {self.ordenacao}"
        if self.limite:
            sql += f" LIMIT {self.limite}"
        if self.offset:
            sql += f" OFFSET {self.offset}"
        return sql


class ConstrutorConsulta:
    """Builder fluente para consultas SQL."""

    def __init__(self, tabela: str):
        self._consulta = Consulta(tabela=tabela)

    def selecionar(self, *campos: str) -> "ConstrutorConsulta":
        self._consulta.campos = list(campos)
        return self

    def onde(self, condicao: str) -> "ConstrutorConsulta":
        self._consulta.condicoes.append(condicao)
        return self

    def ordenar_por(self, campo: str, direcao: str = "ASC") -> "ConstrutorConsulta":
        self._consulta.ordenacao = f"{campo} {direcao}"
        return self

    def limitar(self, n: int) -> "ConstrutorConsulta":
        self._consulta.limite = n
        return self

    def paginar(self, pagina: int, tamanho: int = 10) -> "ConstrutorConsulta":
        self._consulta.limite  = tamanho
        self._consulta.offset  = (pagina - 1) * tamanho
        return self

    def construir(self) -> Consulta:
        return self._consulta


# Interface fluente — method chaining
sql = (
    ConstrutorConsulta("alunos")
    .selecionar("id", "nome", "nota")
    .onde("ativo = true")
    .onde("nota >= 6.0")
    .ordenar_por("nota", "DESC")
    .paginar(pagina=2, tamanho=10)
    .construir()
    .to_sql()
)
print(sql)
# SELECT id, nome, nota FROM alunos WHERE ativo = true
# AND nota >= 6.0 ORDER BY nota DESC LIMIT 10 OFFSET 10

Padrões Estruturais

Adapter

Converte a interface de uma classe em outra que o cliente espera:

from abc import ABC, abstractmethod


class ProcessadorPagamento(ABC):
    @abstractmethod
    def processar(self, valor: float, descricao: str) -> dict:
        ...


# API do Stripe — interface que não controlamos
class StripeAPI:
    def charge(self, amount_cents: int, currency: str, description: str) -> dict:
        return {
            "id":      "ch_123",
            "status":  "succeeded",
            "amount":  amount_cents,
        }


# API do PayPal — outra interface
class PayPalAPI:
    def create_payment(self, total: float, currency: str, note: str) -> dict:
        return {
            "paymentId": "PAY-456",
            "state":     "approved",
            "total":     total
        }


# Adapters — fazem as APIs externas conformarem à nossa interface
class AdapterStripe(ProcessadorPagamento):
    def __init__(self, api: StripeAPI):
        self._api = api

    def processar(self, valor: float, descricao: str) -> dict:
        centavos = int(valor * 100)
        resultado = self._api.charge(centavos, "BRL", descricao)
        return {
            "sucesso":    resultado["status"] == "succeeded",
            "id":         resultado["id"],
            "valor":      resultado["amount"] / 100,
            "gateway":    "stripe"
        }


class AdapterPayPal(ProcessadorPagamento):
    def __init__(self, api: PayPalAPI):
        self._api = api

    def processar(self, valor: float, descricao: str) -> dict:
        resultado = self._api.create_payment(valor, "BRL", descricao)
        return {
            "sucesso":    resultado["state"] == "approved",
            "id":         resultado["paymentId"],
            "valor":      resultado["total"],
            "gateway":    "paypal"
        }


def cobrar(processador: ProcessadorPagamento, valor: float, desc: str):
    """Funciona com qualquer gateway via Adapter."""
    resultado = processador.processar(valor, desc)
    status = "✓" if resultado["sucesso"] else "✗"
    print(f"{status} [{resultado['gateway']}] R${resultado['valor']:.2f} — {desc}")


cobrar(AdapterStripe(StripeAPI()),   150.00, "Mensalidade Março")
cobrar(AdapterPayPal(PayPalAPI()),   150.00, "Mensalidade Março")

Facade

Fornece interface simplificada para um subsistema complexo:

class SubsistemaEmail:
    def conectar(self, host: str): ...
    def autenticar(self, user: str, senha: str): ...
    def enviar(self, para: str, assunto: str, corpo: str): ...
    def desconectar(self): ...


class SubsistemaTemplate:
    def carregar(self, nome: str) -> str: ...
    def renderizar(self, template: str, contexto: dict) -> str: ...


class SubsistemaFila:
    def enfileirar(self, tarefa: dict): ...
    def processar(self): ...


class FacadeNotificacao:
    """
    Facade — simplifica envio de e-mails com template e fila.
    O cliente não precisa conhecer os subsistemas internos.
    """

    def __init__(self):
        self._email    = SubsistemaEmail()
        self._template = SubsistemaTemplate()
        self._fila     = SubsistemaFila()

    def enviar_boas_vindas(self, email: str, nome: str):
        self._fila.enfileirar({
            "tipo":     "email",
            "template": "boas_vindas",
            "para":     email,
            "contexto": {"nome": nome}
        })

    def enviar_boletim(self, email: str, aluno: dict):
        template = self._template.carregar("boletim")
        corpo    = self._template.renderizar(template, aluno)
        self._email.conectar("smtp.gmail.com")
        self._email.autenticar("escola@email.com", "senha")
        self._email.enviar(email, "Seu Boletim", corpo)
        self._email.desconectar()


# Interface simples para o cliente
notif = FacadeNotificacao()
notif.enviar_boas_vindas("ana@email.com", "Ana")

Padrões Comportamentais

Strategy

Define uma família de algoritmos intercambiáveis:

from abc import ABC, abstractmethod
from typing import Protocol


class EstrategiaOrdenacao(Protocol):
    def ordenar(self, alunos: list) -> list: ...


class OrdenarPorNome:
    def ordenar(self, alunos: list) -> list:
        return sorted(alunos, key=lambda a: a["nome"])


class OrdenarPorNota:
    def ordenar(self, alunos: list) -> list:
        return sorted(alunos, key=lambda a: a["nota"], reverse=True)


class OrdenarPorFaltas:
    def ordenar(self, alunos: list) -> list:
        return sorted(alunos, key=lambda a: a["faltas"])


class GerenciadorTurma:
    def __init__(self, estrategia: EstrategiaOrdenacao):
        self._estrategia = estrategia

    def definir_estrategia(self, estrategia: EstrategiaOrdenacao):
        self._estrategia = estrategia

    def listar(self, alunos: list) -> list:
        return self._estrategia.ordenar(alunos)


alunos = [
    {"nome": "Carla", "nota": 8.5, "faltas": 2},
    {"nome": "Ana",   "nota": 9.5, "faltas": 0},
    {"nome": "Bruno", "nota": 7.0, "faltas": 5},
]

gerenciador = GerenciadorTurma(OrdenarPorNota())
print("Por nota:", [a["nome"] for a in gerenciador.listar(alunos)])

gerenciador.definir_estrategia(OrdenarPorNome())
print("Por nome:", [a["nome"] for a in gerenciador.listar(alunos)])

# Versão funcional com Python — Strategy como função
def listar_com_criterio(alunos: list, criterio) -> list:
    return sorted(alunos, key=criterio)

print("Lambda:", [a["nome"] for a in
      listar_com_criterio(alunos, lambda a: -a["nota"])])

Observer

Define dependência um-para-muitos — quando um objeto muda, todos os dependentes são notificados:

from abc import ABC, abstractmethod
from typing import Any
from dataclasses import dataclass
from datetime import datetime


@dataclass
class Evento:
    tipo:      str
    dados:     dict
    timestamp: datetime = None

    def __post_init__(self):
        if self.timestamp is None:
            self.timestamp = datetime.utcnow()


class Observador(ABC):
    @abstractmethod
    def atualizar(self, evento: Evento) -> None: ...


class SistemaEventos:
    """Event bus simples — publisher/subscriber."""

    def __init__(self):
        self._ouvintes: dict[str, list[Observador]] = {}

    def inscrever(self, tipo_evento: str, observador: Observador):
        self._ouvintes.setdefault(tipo_evento, []).append(observador)

    def desinscrever(self, tipo_evento: str, observador: Observador):
        if tipo_evento in self._ouvintes:
            self._ouvintes[tipo_evento].discard(observador)

    def publicar(self, evento: Evento):
        for obs in self._ouvintes.get(evento.tipo, []):
            try:
                obs.atualizar(evento)
            except Exception as e:
                print(f"Erro no observador {obs}: {e}")

        # Ouvintes de todos os eventos
        for obs in self._ouvintes.get("*", []):
            obs.atualizar(evento)


# Observadores concretos
class NotificadorEmail(Observador):
    def atualizar(self, evento: Evento):
        if evento.tipo == "aluno.aprovado":
            print(f"[Email] Parabéns {evento.dados['nome']}! Você foi aprovado.")

class AuditoriaLogger(Observador):
    def atualizar(self, evento: Evento):
        print(f"[Auditoria] {evento.timestamp} | {evento.tipo} | {evento.dados}")

class AtualizadorPainel(Observador):
    def atualizar(self, evento: Evento):
        if evento.tipo in ["aluno.aprovado", "aluno.reprovado"]:
            print(f"[Painel] Atualizando dashboard...")


# Uso
bus = SistemaEventos()
bus.inscrever("aluno.aprovado",  NotificadorEmail())
bus.inscrever("aluno.aprovado",  AuditoriaLogger())
bus.inscrever("aluno.reprovado", AuditoriaLogger())
bus.inscrever("*",               AtualizadorPainel())

bus.publicar(Evento(
    tipo="aluno.aprovado",
    dados={"id": 1, "nome": "Ana Silva", "nota": 9.5}
))
bus.publicar(Evento(
    tipo="aluno.reprovado",
    dados={"id": 2, "nome": "Bruno Costa", "nota": 4.0}
))

Command

Encapsula uma requisição como objeto — permite desfazer, enfileirar e logar:

from abc import ABC, abstractmethod
from typing import Optional
from collections import deque


class Comando(ABC):
    @abstractmethod
    def executar(self) -> None: ...

    @abstractmethod
    def desfazer(self) -> None: ...


class ComandoAdicionarNota(Comando):
    def __init__(self, aluno: dict, nota: float):
        self.aluno          = aluno
        self.nota           = nota
        self._nota_anterior: Optional[float] = None

    def executar(self) -> None:
        self._nota_anterior = self.aluno.get("nota")
        self.aluno["nota"]  = self.nota
        print(f"Nota {self.nota} adicionada para {self.aluno['nome']}")

    def desfazer(self) -> None:
        self.aluno["nota"] = self._nota_anterior
        print(f"Nota desfeita para {self.aluno['nome']}: {self._nota_anterior}")


class ComandoDesativarAluno(Comando):
    def __init__(self, aluno: dict):
        self.aluno = aluno

    def executar(self) -> None:
        self.aluno["ativo"] = False
        print(f"Aluno {self.aluno['nome']} desativado")

    def desfazer(self) -> None:
        self.aluno["ativo"] = True
        print(f"Aluno {self.aluno['nome']} reativado")


class GerenciadorComandos:
    """Controla execução e histórico de comandos — suporta undo/redo."""

    def __init__(self):
        self._historico:  deque[Comando] = deque(maxlen=50)
        self._desfeitos:  deque[Comando] = deque(maxlen=50)

    def executar(self, comando: Comando) -> None:
        comando.executar()
        self._historico.append(comando)
        self._desfeitos.clear()

    def desfazer(self) -> None:
        if not self._historico:
            print("Nada para desfazer.")
            return
        comando = self._historico.pop()
        comando.desfazer()
        self._desfeitos.append(comando)

    def refazer(self) -> None:
        if not self._desfeitos:
            print("Nada para refazer.")
            return
        comando = self._desfeitos.pop()
        comando.executar()
        self._historico.append(comando)


aluno = {"nome": "Ana", "nota": 7.0, "ativo": True}
gerenciador = GerenciadorComandos()

gerenciador.executar(ComandoAdicionarNota(aluno, 9.5))
gerenciador.executar(ComandoDesativarAluno(aluno))
print(f"Estado: {aluno}")

gerenciador.desfazer()
gerenciador.desfazer()
print(f"Após desfazer: {aluno}")

gerenciador.refazer()
print(f"Após refazer: {aluno}")

Chain of Responsibility

Passa uma requisição por uma cadeia de handlers:

from abc import ABC, abstractmethod
from typing import Optional
from dataclasses import dataclass


@dataclass
class Pedido:
    valor:       float
    tipo:        str
    usuario_id:  int
    aprovado:    bool  = False
    motivo:      str   = ""


class Handler(ABC):
    def __init__(self):
        self._proximo: Optional["Handler"] = None

    def definir_proximo(self, handler: "Handler") -> "Handler":
        self._proximo = handler
        return handler

    def processar(self, pedido: Pedido) -> Pedido:
        if self._proximo:
            return self._proximo.processar(pedido)
        return pedido

    @abstractmethod
    def handle(self, pedido: Pedido) -> Optional[Pedido]: ...


class ValidacaoHandler(Handler):
    def handle(self, pedido: Pedido) -> Optional[Pedido]:
        if pedido.valor <= 0:
            pedido.motivo   = "Valor inválido"
            pedido.aprovado = False
            return pedido
        return self.processar(pedido)


class LimiteHandler(Handler):
    def __init__(self, limite: float):
        super().__init__()
        self.limite = limite

    def handle(self, pedido: Pedido) -> Optional[Pedido]:
        if pedido.valor > self.limite:
            pedido.motivo   = f"Valor acima do limite R${self.limite:.2f}"
            pedido.aprovado = False
            return pedido
        return self.processar(pedido)


class FraudeHandler(Handler):
    def handle(self, pedido: Pedido) -> Optional[Pedido]:
        if pedido.usuario_id in [666, 999]:
            pedido.motivo   = "Usuário bloqueado por suspeita de fraude"
            pedido.aprovado = False
            return pedido
        return self.processar(pedido)


class AprovacaoFinalHandler(Handler):
    def handle(self, pedido: Pedido) -> Optional[Pedido]:
        pedido.aprovado = True
        pedido.motivo   = "Aprovado"
        return pedido


# Montando a cadeia
validacao = ValidacaoHandler()
limite    = LimiteHandler(5000.0)
fraude    = FraudeHandler()
aprovacao = AprovacaoFinalHandler()

validacao.definir_proximo(limite).definir_proximo(fraude).definir_proximo(aprovacao)

pedidos = [
    Pedido(valor=250.00,  tipo="compra",    usuario_id=1),
    Pedido(valor=-10.00,  tipo="compra",    usuario_id=2),
    Pedido(valor=8000.00, tipo="saque",     usuario_id=3),
    Pedido(valor=100.00,  tipo="compra",    usuario_id=666),
]

for p in pedidos:
    resultado = validacao.handle(p)
    status = "✓" if resultado.aprovado else "✗"
    print(f"{status} R${p.valor:>8.2f} | {resultado.motivo}")

Template Method

Define o esqueleto de um algoritmo, deixando subclasses preencherem os detalhes:

from abc import ABC, abstractmethod


class ProcessadorRelatorio(ABC):
    """Template Method — define o fluxo, subclasses definem os passos."""

    def gerar(self, dados: list) -> str:
        """Método template — não deve ser sobrescrito."""
        dados_limpos  = self.limpar_dados(dados)
        dados_proc    = self.processar(dados_limpos)
        conteudo      = self.formatar(dados_proc)
        cabecalho     = self.cabecalho()
        rodape        = self.rodape()
        return f"{cabecalho}\n{conteudo}\n{rodape}"

    def limpar_dados(self, dados: list) -> list:
        """Hook — comportamento padrão pode ser sobrescrito."""
        return [d for d in dados if d is not None]

    @abstractmethod
    def processar(self, dados: list) -> list: ...

    @abstractmethod
    def formatar(self, dados: list) -> str: ...

    def cabecalho(self) -> str:
        return "=== RELATÓRIO ==="

    def rodape(self) -> str:
        return f"Total: {0} registros"


class RelatorioAprovados(ProcessadorRelatorio):
    def processar(self, dados: list) -> list:
        return [d for d in dados if d.get("nota", 0) >= 6.0]

    def formatar(self, dados: list) -> str:
        linhas = [f"  ✓ {d['nome']:20} — {d['nota']:.1f}" for d in dados]
        return "\n".join(linhas)

    def rodape(self) -> str:
        return "Aprovados listados acima."


class RelatorioRanking(ProcessadorRelatorio):
    def processar(self, dados: list) -> list:
        return sorted(dados, key=lambda d: d.get("nota", 0), reverse=True)

    def formatar(self, dados: list) -> str:
        linhas = [
            f"  {i:2}. {d['nome']:20} — {d['nota']:.1f}"
            for i, d in enumerate(dados, 1)
        ]
        return "\n".join(linhas)

    def cabecalho(self) -> str:
        return "=== RANKING DE NOTAS ==="


alunos = [
    {"nome": "Ana",   "nota": 9.5},
    {"nome": "Bruno", "nota": 4.0},
    {"nome": "Carla", "nota": 8.5},
    {"nome": "Diego", "nota": 6.0},
]

print(RelatorioAprovados().gerar(alunos))
print()
print(RelatorioRanking().gerar(alunos))

Exemplo Completo: Sistema de Processamento de Pagamentos

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Optional, Protocol
from datetime import datetime
from collections import deque
import uuid


# ── Domínio ───────────────────────────────────────────
@dataclass
class Pagamento:
    id:          str          = field(default_factory=lambda: str(uuid.uuid4())[:8])
    valor:       float        = 0.0
    descricao:   str          = ""
    gateway:     str          = ""
    status:      str          = "pendente"
    timestamp:   datetime     = field(default_factory=datetime.utcnow)
    tentativas:  int          = 0
    erro:        Optional[str] = None


# ── Observer: eventos do pagamento ────────────────────
class ObservadorPagamento(Protocol):
    def on_pagamento(self, pagamento: Pagamento) -> None: ...


class LogPagamento:
    def on_pagamento(self, p: Pagamento):
        print(f"  [Log] {p.status.upper()} | {p.id} | R${p.valor:.2f}")


class NotificadorPagamento:
    def on_pagamento(self, p: Pagamento):
        if p.status == "aprovado":
            print(f"  [Email] Pagamento de R${p.valor:.2f} aprovado!")
        elif p.status == "recusado":
            print(f"  [Email] Pagamento recusado: {p.erro}")


# ── Strategy: gateways ────────────────────────────────
class GatewayPagamento(Protocol):
    def cobrar(self, valor: float, descricao: str) -> dict: ...


class GatewaySimulado:
    def __init__(self, nome: str, taxa_sucesso: float = 0.9):
        self.nome        = nome
        self.taxa_sucesso = taxa_sucesso
        import random
        self._rand = random.Random(42)

    def cobrar(self, valor: float, descricao: str) -> dict:
        sucesso = self._rand.random() < self.taxa_sucesso
        return {
            "sucesso":     sucesso,
            "id_externo":  f"{self.nome[:3].upper()}-{uuid.uuid4().hex[:6]}",
            "erro":        None if sucesso else "Cartão recusado"
        }


# ── Chain: validações ─────────────────────────────────
class ValidadorPagamento(ABC):
    def __init__(self):
        self._proximo: Optional["ValidadorPagamento"] = None

    def proximo(self, v: "ValidadorPagamento") -> "ValidadorPagamento":
        self._proximo = v
        return v

    def validar(self, p: Pagamento) -> bool:
        resultado = self._checar(p)
        if resultado and self._proximo:
            return self._proximo.validar(p)
        return resultado

    @abstractmethod
    def _checar(self, p: Pagamento) -> bool: ...


class ValidarValor(ValidadorPagamento):
    def _checar(self, p: Pagamento) -> bool:
        if p.valor <= 0:
            p.status = "recusado"
            p.erro   = "Valor inválido"
            return False
        return True


class ValidarLimite(ValidadorPagamento):
    def __init__(self, limite: float):
        super().__init__()
        self.limite = limite

    def _checar(self, p: Pagamento) -> bool:
        if p.valor > self.limite:
            p.status = "recusado"
            p.erro   = f"Acima do limite R${self.limite:.2f}"
            return False
        return True


# ── Facade: orquestra tudo ────────────────────────────
class ProcessadorPagamentos:
    def __init__(self, gateway: GatewayPagamento):
        self._gateway     = gateway
        self._observadores: list = [LogPagamento(), NotificadorPagamento()]
        self._historico:  deque[Pagamento] = deque(maxlen=100)

        # Cadeia de validação
        v1 = ValidarValor()
        v2 = ValidarLimite(10_000.0)
        v1.proximo(v2)
        self._validador = v1

    def processar(self, valor: float, descricao: str) -> Pagamento:
        p = Pagamento(valor=valor, descricao=descricao)
        print(f"\nProcessando: R${valor:.2f} — {descricao}")

        # Validação (Chain of Responsibility)
        if not self._validador.validar(p):
            self._notificar(p)
            self._historico.append(p)
            return p

        # Cobrança (Strategy)
        p.tentativas += 1
        resultado     = self._gateway.cobrar(valor, descricao)

        if resultado["sucesso"]:
            p.status  = "aprovado"
            p.gateway = resultado["id_externo"]
        else:
            p.status  = "recusado"
            p.erro    = resultado["erro"]

        # Notificação (Observer)
        self._notificar(p)
        self._historico.append(p)
        return p

    def _notificar(self, p: Pagamento):
        for obs in self._observadores:
            obs.on_pagamento(p)

    def relatorio(self) -> dict:
        aprovados  = [p for p in self._historico if p.status == "aprovado"]
        recusados  = [p for p in self._historico if p.status == "recusado"]
        return {
            "total":        len(self._historico),
            "aprovados":    len(aprovados),
            "recusados":    len(recusados),
            "valor_total":  sum(p.valor for p in aprovados),
        }


# ── Executando ────────────────────────────────────────
gateway    = GatewaySimulado("Stripe", taxa_sucesso=0.8)
processador = ProcessadorPagamentos(gateway)

pagamentos = [
    (250.00,   "Mensalidade Março"),
    (-10.00,   "Valor inválido"),
    (15000.00, "Acima do limite"),
    (89.90,    "Material didático"),
    (500.00,   "Matrícula"),
]

for valor, desc in pagamentos:
    processador.processar(valor, desc)

print("\n=== Relatório ===")
rel = processador.relatorio()
for k, v in rel.items():
    print(f"  {k:15}: {v}")

Resumo

  • Singleton garante instância única — use com moderação, dificulta testes
  • Factory Method centraliza criação de objetos — extensível sem modificar código existente
  • Builder constrói objetos complexos passo a passo com interface fluente
  • Adapter faz interfaces incompatíveis funcionarem juntas — essencial para integrar APIs externas
  • Facade simplifica subsistemas complexos com uma interface única e coesa
  • Strategy torna algoritmos intercambiáveis — elimina condicionais em código de negócio
  • Observer desacopla produtor de consumidores — base de sistemas orientados a eventos
  • Command encapsula operações como objetos — habilita undo, redo e histórico
  • Chain of Responsibility passa requisições por validadores encadeados — pipelines de processamento
  • Template Method define esqueleto de algoritmo — subclasses preenchem os detalhes

Referências e Leituras Complementares

  • Refactoring Guru — Padrões de Projeto — https://refactoring.guru/pt-br/design-patterns
  • Python Patterns — repositório — https://github.com/faif/python-patterns
  • GAMMA, Erich et al. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994. — o livro original do Gang of Four.
  • PHILLIPS, Dusty; LOTT, Steven F. Python Object-Oriented Programming. 4. ed. Packt, 2021. Cap. 10–12 — design patterns com exemplos modernos em Python.
  • PERCIVAL, Harry; GREGORY, Bob. Architecture Patterns with Python. O'Reilly Media, 2020. — Repository, Unit of Work, Event-Driven Architecture em Python.

Prof. Ricardo Matos — Dominando o Python em 1 Ano · Artigo 47 de 52


✅ Módulo 8 Concluído — Testes, Qualidade e Boas Práticas


Plano Completo da Série — Estado Atual

Módulo Tema Artigos Status
1 Fundamentos da Linguagem 01–06 ✅ Concluído
2 Estruturas de Dados e Algoritmos 07–12 ✅ Concluído
3 Orientação a Objetos 13–18 ✅ Concluído
4 Arquivos, I/O e Banco de Dados 19–24 ✅ Concluído
5 Python para Web 25–30 ✅ Concluído
6 Automação e Scripts 31–34 ✅ Concluído
7 Data Science e Machine Learning 35–42 ✅ Concluído
8 Testes, Qualidade e Boas Práticas 43–47 ✅ Concluído
9 Projetos Reais e Carreira 48–52 ⬜ Pendente

Próximo: Módulo 9 · Artigo 48 — Projeto 1: API REST completa com FastAPI, PostgreSQL e Docker

Digite "Sim" para continuar.


you asked

Sim


claude response

Dominando o Python em 1 Ano

Comentários

Mais em Python

WebSockets e Comunicação em Tempo Real em Python (Ver se é duplicidade)
WebSockets e Comunicação em Tempo Real em Python (Ver se é duplicidade)

HTTP é excelente para o modelo requisição-resposta — o cliente pergunta, o se...

Estruturas de Controle: if, elif e else
Estruturas de Controle: if, elif e else

Um programa que executa sempre as mesmas instru&ccedil;&otilde;es na mesma or...

Artigo 37 — Redes Neurais com TensorFlow e Keras
Artigo 37 — Redes Neurais com TensorFlow e Keras

Redes neurais artificiais são o motor por trás do reconhecimento de imagens,...