Módulo 10: Paradigmas & Padrões • Bloco 2: SOLID & GRASP • Trilha em 4 Níveis


🎯 Nível 1: Fundamentos

Problema 08.1 — Pure Fabrication: Isolando Persistência de Dados

Contexto: Colocar comandos SQL diretamente dentro da classe de domínio Usuario violaria a Alta Coesão e o Baixo Acoplamento.

Requisitos de Execução:

  1. Crie a entidade de domínio puro Usuario (com atributos id, nome e email).
  2. Crie a classe de Pure Fabrication UsuarioRepository responsável exclusivamente por salvar, consultar e simular a persistência em memória.

Resultado Esperado

=== Pure Fabrication: Repositório de Usuários ===
[Domínio Puro] Criado objeto Usuario: Rodrigo (rodrigo@email.com)
[Pure Fabrication] Persistindo usuário #401 no Repositório...
[Consulta] Usuário recuperado com sucesso: Rodrigo <rodrigo@email.com>

📤 Instruções de Entrega (Microsoft Teams)

  1. Salve o arquivo como: Atividade_08_1_SeuNome.py
  2. Envie na tarefa: Atividade Cap 08 - Padrões GRASP Avançados
🔑 Gabarito de Código & Solução Comentada
# Atividade_08_1_Gabarito.py
from typing import Dict, Optional

# 1. Entidade de Domínio Puro
class Usuario:
    def __init__(self, id_usuario: int, nome: str, email: str):
        self.id = id_usuario
        self.nome = nome
        self.email = email

# 2. Pure Fabrication: Classe de infraestrutura artificial
class UsuarioRepository:
    def __init__(self):
        self._tabela_db: Dict[int, Usuario] = {}

    def salvar(self, usuario: Usuario) -> None:
        self._tabela_db[usuario.id] = usuario
        print(f"[Pure Fabrication] Persistindo usuário #{usuario.id} no Repositório...")

    def buscar_por_id(self, id_usuario: int) -> Optional[Usuario]:
        return self._tabela_db.get(id_usuario)

if __name__ == "__main__":
    print("=== Pure Fabrication: Repositório de Usuários ===")
    user = Usuario(401, "Rodrigo", "rodrigo@email.com")
    print(f"[Domínio Puro] Criado objeto Usuario: {user.nome} ({user.email})")

    repo = UsuarioRepository()
    repo.salvar(user)

    res = repo.buscar_por_id(401)
    if res:
        print(f"[Consulta] Usuário recuperado com sucesso: {res.nome} <{res.email}>")

🔍 Nível 2: Prática

Problema 08.2 — Polymorphism GRASP: Eliminando Estruturas Condicionais de Tipo

Contexto: O padrão Polymorphism do GRASP orienta substituir verificações condicionais repetitivas (if tipo == 'FISICA') por despacho polimórfico dinâmico.

Requisitos de Execução:

  1. Crie a classe abstrata Pessoa com método calcular_tributo_anual(rendimento: float) -> float.
  2. Implemente PessoaFisica (alíquota progressiva de $27.5\%$) e PessoaJuridica (alíquota do Simples Nacional de $6\%$).
  3. O ProcessadorContabil deve calcular impostos sobre uma lista heterogênea sem nenhum isinstance ou type().

Resultado Esperado

=== Processador Contábil Polimórfico (GRASP) ===
[PessoaFísica: Mariana] Rendimento: R$ 100000.00 -> Tributo Devido: R$ 27500.00
[PessoaJurídica: TechCorp] Faturamento: R$ 500000.00 -> Tributo Devido: R$ 30000.00
Total Geral Arrecadado: R$ 57500.00

📤 Instruções de Entrega (Microsoft Teams)

  1. Salve o arquivo como: Atividade_08_2_SeuNome.py
  2. Envie na tarefa: Atividade Cap 08 - Padrões GRASP Avançados
🔑 Gabarito de Código & Solução Comentada
# Atividade_08_2_Gabarito.py
from abc import ABC, abstractmethod
from typing import List, Tuple

class Pessoa(ABC):
    def __init__(self, nome: str):
        self.nome = nome

    @abstractmethod
    def calcular_tributo(self, rendimento: float) -> float: pass

class PessoaFisica(Pessoa):
    def calcular_tributo(self, rendimento: float) -> float:
        return rendimento * 0.275

class PessoaJuridica(Pessoa):
    def calcular_tributo(self, rendimento: float) -> float:
        return rendimento * 0.06

if __name__ == "__main__":
    contribuintes: List[Tuple[Pessoa, float]] = [
        (PessoaFisica("Mariana"), 100000.00),
        (PessoaJuridica("TechCorp"), 500000.00)
    ]

    print("=== Processador Contábil Polimórfico (GRASP) ===")
    total = 0.0
    for p, valor in contribuintes:
        tributo = p.calcular_tributo(valor)
        total += tributo
        tipo = type(p).__name__
        print(f"[{tipo}: {p.nome}] Base: R$ {valor:.2f} -> Tributo Devido: R$ {tributo:.2f}")

    print(f"Total Geral Arrecadado: R$ {total:.2f}")

🚀 Nível 3: Integração

Problema 08.3 — Indirection & Protected Variations em Gateway de SMS

Contexto: Operadoras de SMS mudam com frequência. O padrão Indirection cria um intermediário que desacopla o sistema da API concreta de terceiros, enquanto Protected Variations protege os clientes contra variações de contrato.

Requisitos de Execução:

  1. Crie a interface estável ISMSGateway (Protected Variations).
  2. Implemente o intermediário SMSDispatcher (Indirection) que gerencia failover automático entre TwilioServiceAdapter e ZenviaServiceAdapter.
  3. Caso a operadora primária falhe, o despachante chaveia transparentemente para a secundária.

Resultado Esperado

=== Despacho de Mensageria com Indireção & Failover ===
Tentando envio via Operadora Primária (Twilio)...
[Twilio Adapter] ERRO: 503 Service Unavailable!
[Indirection] Ativando failover para Zenvia...
[Zenvia Adapter] SMS 'Código: 8819' entregue com sucesso!

📤 Instruções de Entrega (Microsoft Teams)

  1. Salve o arquivo como: Atividade_08_3_SeuNome.py
  2. Envie na tarefa: Atividade Cap 08 - Padrões GRASP Avançados
🔑 Gabarito de Código & Solução Comentada
# Atividade_08_3_Gabarito.py
from abc import ABC, abstractmethod

# Protected Variations: Interface estável
class ISMSGateway(ABC):
    @abstractmethod
    def enviar_sms(self, numero: str, mensagem: str) -> bool: pass

class TwilioServiceAdapter(ISMSGateway):
    def enviar_sms(self, numero: str, mensagem: str) -> bool:
        print("[Twilio Adapter] ERRO: 503 Service Unavailable!")
        return False

class ZenviaServiceAdapter(ISMSGateway):
    def enviar_sms(self, numero: str, mensagem: str) -> bool:
        print(f"[Zenvia Adapter] SMS '{mensagem}' entregue com sucesso!")
        return True

# Indirection: Intermediário que encapsula a lógica de failover
class SMSDispatcher:
    def __init__(self, primario: ISMSGateway, secundario: ISMSGateway):
        self._primario = primario
        self._secundario = secundario

    def disparar(self, numero: str, mensagem: str):
        print("Tentando envio via Operadora Primária (Twilio)...")
        sucesso = self._primario.enviar_sms(numero, mensagem)
        if not sucesso:
            print("[Indirection] Ativando failover para Zenvia...")
            self._secundario.enviar_sms(numero, mensagem)

if __name__ == "__main__":
    print("=== Despacho de Mensageria com Indireção & Failover ===")
    dispatcher = SMSDispatcher(TwilioServiceAdapter(), ZenviaServiceAdapter())
    dispatcher.disparar("+5511999998888", "Código: 8819")

🏆 Nível 4: Desafio

Problema 08.4 — Suíte Completa dos 9 Padrões GRASP em um Motor de Empréstimos Bancários

Contexto: Combine os 9 padrões GRASP (Expert, Creator, Controller, Low Coupling, High Cohesion, Polymorphism, Pure Fabrication, Indirection, Protected Variations) em um motor corporativo de concessão de crédito.

Requisitos de Execução:

  1. Controller: EmprestimoController recebe o pedido.
  2. Creator / Expert: PropostaCredito instancia suas Parcela e calcula o Custo Efetivo Total (CET).
  3. Polymorphism: Subtipos de PoliticaRisco (Pessoa Física vs Produtor Rural).
  4. Pure Fabrication: AuditoriaService grava eventos de conformidade.
  5. Indirection & Protected Variations: MotorBureauCredito protege o sistema contra mudanças na API da Serasa.

Resultado Esperado

=== Motor Corporativo GRASP de Crédito ===
[Controller] Recebida proposta de R$ 50000.00 em 12 parcelas
[Indirection Bureau] Consulta Score Serasa: 850 (Risco Baixo)
[Polymorphism] Política PF aplicada: Taxa de 1.49% a.m.
[Creator/Expert] 12 parcelas de R$ 4583.12 geradas. CET Total: R$ 54997.44
[Pure Fabrication] Auditoria registrada: Proposta aprovada em conformidade Bacen.

📤 Instruções de Entrega (Microsoft Teams)

  1. Salve o arquivo como: Atividade_08_4_SeuNome.py
  2. Envie na tarefa: Atividade Cap 08 - Padrões GRASP Avançados
🔑 Gabarito de Código & Solução Comentada
# Atividade_08_4_Gabarito.py
from abc import ABC, abstractmethod
from typing import List

# Protected Variations & Indirection
class IBureauCredito(ABC):
    @abstractmethod
    def consultar_score(self, doc: str) -> int: pass

class MockBureauSerasa(IBureauCredito):
    def consultar_score(self, doc: str) -> int:
        print("[Indirection Bureau] Consulta Score Serasa: 850 (Risco Baixo)")
        return 850

# Polymorphism
class IPoliticaRisco(ABC):
    @abstractmethod
    def calcular_taxa(self, score: int) -> float: pass

class PoliticaRiscoPF(IPoliticaRisco):
    def calcular_taxa(self, score: int) -> float:
        print("[Polymorphism] Política PF aplicada: Taxa de 1.49% a.m.")
        return 0.0149

# Pure Fabrication
class AuditoriaService:
    def registrar(self, msg: str):
        print(f"[Pure Fabrication] Auditoria registrada: {msg}")

# Creator & Information Expert
class Parcela:
    def __init__(self, numero: int, valor: float):
        self.numero = numero
        self.valor = valor

class PropostaCredito:
    def __init__(self, valor_total: float, qtd_parcelas: int, taxa: float):
        self.valor_total = valor_total
        self.qtd_parcelas = qtd_parcelas
        self.taxa = taxa
        self.parcelas: List[Parcela] = []

    def gerar_parcelas(self):
        valor_parcela = (self.valor_total * (1 + self.taxa * self.qtd_parcelas)) / self.qtd_parcelas
        for i in range(1, self.qtd_parcelas + 1):
            self.parcelas.append(Parcela(i, valor_parcela))
        cet_total = valor_parcela * self.qtd_parcelas
        print(f"[Creator/Expert] {self.qtd_parcelas} parcelas de R$ {valor_parcela:.2f} geradas. CET Total: R$ {cet_total:.2f}")

# Controller
class EmprestimoController:
    def __init__(self, bureau: IBureauCredito, politica: IPoliticaRisco, audit: AuditoriaService):
        self._bureau = bureau
        self._politica = politica
        self._audit = audit

    def submeter_proposta(self, doc: str, valor: float, parcelas: int):
        print(f"[Controller] Recebida proposta de R$ {valor:.2f} em {parcelas} parcelas")
        score = self._bureau.consultar_score(doc)
        taxa = self._politica.calcular_taxa(score)
        prop = PropostaCredito(valor, parcelas, taxa)
        prop.gerar_parcelas()
        self._audit.registrar("Proposta aprovada em conformidade Bacen.")

if __name__ == "__main__":
    print("=== Motor Corporativo GRASP de Crédito ===")
    ctrl = EmprestimoController(MockBureauSerasa(), PoliticaRiscoPF(), AuditoriaService())
    ctrl.submeter_proposta("123.456.789-00", 50000.0, 12)