Módulo 10: Paradigmas & Padrões • Bloco 2: SOLID & GRASP • Demonstrações Práticas

🏛️ Atribuição Racional de Responsabilidades na Prática: Demonstrações executáveis dos cinco padrões GRASP fundamentais de Craig Larman. No Exemplo 1, projetamos um Ponto de Venda (PDV) onde Information Expert e Creator atuam harmoniosamente para encapsular estado e criação. No Exemplo 2, implementamos um Use-Case Controller coeso desacoplado da interface de entrada, garantindo Baixo Acoplamento e Alta Coesão.


1. Ponto de Venda (PDV): Information Expert e Creator

Neste exemplo, aplicamos o padrão Creator para que a classe Venda instancie e agregue seus próprios itens (ItemVenda), e o padrão Information Expert para que o cálculo financeiro permaneça onde os dados residem:

# grasp_expert_creator_pdv.py
from dataclasses import dataclass
from typing import List, Dict

# Entidade do Catálogo de Produtos (Imutável)
@dataclass(frozen=True)
class EspecificacaoProduto:
    codigo_barras: str
    descricao: str
    preco_unitario: float
    aliquota_imposto: float

# Linha da Venda
class ItemVenda:
    def __init__(self, produto: EspecificacaoProduto, quantidade: int):
        self.produto = produto
        self.quantidade = quantidade

    # 1. Information Expert: Conhece o produto e a quantidade da linha
    def calcular_subtotal_bruto(self) -> float:
        return self.produto.preco_unitario * self.quantidade

    def calcular_imposto_item(self) -> float:
        return self.calcular_subtotal_bruto() * self.produto.aliquota_imposto

# Agregador da Venda
class Venda:
    def __init__(self, id_venda: str):
        self.id_venda = id_venda
        self._itens: List[ItemVenda] = []
        self._desconto_global: float = 0.0

    # 2. Creator: A Venda agrega, contém e tem o contexto para criar ItemVenda
    def adicionar_item(self, produto: EspecificacaoProduto, quantidade: int) -> ItemVenda:
        item = ItemVenda(produto, quantidade)
        self._itens.append(item)
        return item

    def definir_desconto(self, valor_desconto: float):
        self._desconto_global = valor_desconto

    # 3. Information Expert: Conhece todas as linhas de item para calcular o total
    def calcular_total_bruto(self) -> float:
        return sum(item.calcular_subtotal_bruto() for item in self._itens)

    def calcular_total_impostos(self) -> float:
        return sum(item.calcular_imposto_item() for item in self._itens)

    def calcular_total_liquidado(self) -> float:
        subtotal = self.calcular_total_bruto()
        return max(0.0, subtotal - self._desconto_global) + self.calcular_total_impostos()

    def gerar_recibo(self) -> str:
        linhas = [f"=== RECIBO DE VENDA #{self.id_venda} ==="]
        for it in self._itens:
            linhas.append(
                f"  {it.quantidade}x {it.produto.descricao:25} | "
                f"Un: R$ {it.produto.preco_unitario:6.2f} | "
                f"Sub: R$ {it.calcular_subtotal_bruto():7.2f}"
            )
        linhas.append("-" * 55)
        linhas.append(f"  Subtotal Bruto:       R$ {self.calcular_total_bruto():9.2f}")
        linhas.append(f"  Desconto Concedido:  -R$ {self._desconto_global:9.2f}")
        linhas.append(f"  Tributos Incidentes: +R$ {self.calcular_total_impostos():9.2f}")
        linhas.append(f"  TOTAL A PAGAR:        R$ {self.calcular_total_liquidado():9.2f}")
        return "\n".join(linhas)

# Execução Prática
if __name__ == "__main__":
    catalogo: Dict[str, EspecificacaoProduto] = {
        "789001": EspecificacaoProduto("789001", "Café Gourmet 500g", 28.50, 0.07),
        "789002": EspecificacaoProduto("789002", "Leite Integral 1L", 4.80, 0.05),
        "789003": EspecificacaoProduto("789003", "Chocolate Amargo 70%", 12.00, 0.12),
    }

    venda = Venda("PDV-2026-901")
    # Venda atua como Criador dos seus próprios itens
    venda.adicionar_item(catalogo["789001"], 2)
    venda.adicionar_item(catalogo["789002"], 4)
    venda.adicionar_item(catalogo["789003"], 1)
    venda.definir_desconto(5.00)

    print(venda.gerar_recibo())

2. Orquestração com Use-Case Controller e Baixo Acoplamento

Neste exemplo, aplicamos o padrão Controller para separar a camada de apresentação da lógica de domínio, preservando a Alta Coesão e o Baixo Acoplamento:

# grasp_controller_cohesion.py
from typing import Optional, Dict

class CatalogoProdutosMock:
    def __init__(self):
        self._produtos = {
            "SKU_10": EspecificacaoProduto("SKU_10", "Monitor Ultrawide 34'", 2800.0, 0.15),
            "SKU_20": EspecificacaoProduto("SKU_20", "Teclado Mecânico Ergonômico", 450.0, 0.10)
        }

    def buscar_por_codigo(self, codigo: str) -> Optional[EspecificacaoProduto]:
        return self._produtos.get(codigo)

class RepositorioVendasMock:
    def __init__(self):
        self._armazenamento = {}

    def salvar(self, venda: Venda):
        self._armazenamento[venda.id_venda] = venda
        print(f"  💾 [Repositório] Venda #{venda.id_venda} persistida com sucesso no banco de dados.")

# Controller de Caso de Uso: Alto Nível, Alta Coesão, Sem Código Visual
class ProcessarVendaController:
    """Orquestrador do caso de uso 'Realizar Venda'. Não contém código de tela nem SQL direto."""
    def __init__(self, catalogo: CatalogoProdutosMock, repositorio: RepositorioVendasMock):
        self.catalogo = catalogo
        self.repositorio = repositorio
        self._venda_corrente: Optional[Venda] = None

    def iniciar_nova_venda(self, id_venda: str):
        self._venda_corrente = Venda(id_venda)
        print(f"\n[Controller] Sessão de venda #{id_venda} iniciada.")

    def registrar_leitura_item(self, codigo_produto: str, quantidade: int) -> bool:
        if self._venda_corrente is None:
            raise RuntimeError("Nenhuma venda em andamento. Inicie a venda primeiro.")

        produto = self.catalogo.buscar_por_codigo(codigo_produto)
        if produto is None:
            print(f"  ❌ [Controller] Erro: Produto com código '{codigo_produto}' não cadastrado.")
            return False

        # Delegação: Creator e Expert entram em ação dentro da Venda
        self._venda_corrente.adicionar_item(produto, quantidade)
        print(f"  🛒 [Controller] Item adicionado: {quantidade}x {produto.descricao}")
        return True

    def aplicar_cupom(self, valor_desconto: float):
        if self._venda_corrente:
            self._venda_corrente.definir_desconto(valor_desconto)
            print(f"  🏷️ [Controller] Cupom de R$ {valor_desconto:.2f} aplicado à venda.")

    def finalizar_venda(self) -> float:
        if self._venda_corrente is None:
            raise RuntimeError("Nenhuma venda em andamento.")

        total = self._venda_corrente.calcular_total_liquidado()
        self.repositorio.salvar(self._venda_corrente)
        print(f"  ✅ [Controller] Venda finalizada com êxito! Total: R$ {total:.2f}")
        venda_fechada = self._venda_corrente
        self._venda_corrente = None
        return total

# Simulação da Camada de Apresentação (Interface UI/Terminal)
if __name__ == "__main__":
    print("\n" + "=" * 65)
    print("🎮 SIMULAÇÃO DA CAMADA DE APRESENTAÇÃO VIA CONTROLLER GRASP")
    print("=" * 65)

    controller = ProcessarVendaController(CatalogoProdutosMock(), RepositorioVendasMock())
    
    # 1. Usuário clica no botão "Nova Venda"
    controller.iniciar_nova_venda("VND-2026-0042")

    # 2. Leitor óptico bipou itens
    controller.registrar_leitura_item("SKU_10", 1)
    controller.registrar_leitura_item("SKU_20", 2)
    controller.registrar_leitura_item("CODIGO_INVALIDO", 1) # Tratamento de erro pelo controller

    # 3. Operador aplicou desconto promocional
    controller.aplicar_cupom(100.00)

    # 4. Pagamento efetuado e encerramento
    controller.finalizar_venda()

Saída Esperada dos Scripts:

=== RECIBO DE VENDA #PDV-2026-901 ===
  2x Café Gourmet 500g         | Un: R$  28.50 | Sub: R$   57.00
  4x Leite Integral 1L         | Un: R$   4.80 | Sub: R$   19.20
  1x Chocolate Amargo 70%      | Un: R$  12.00 | Sub: R$   12.00
-------------------------------------------------------
  Subtotal Bruto:       R$     88.20
  Desconto Concedido:  -R$      5.00
  Tributos Incidentes: +R$      6.39
  TOTAL A PAGAR:        R$     89.59

=================================================================
🎮 SIMULAÇÃO DA CAMADA DE APRESENTAÇÃO VIA CONTROLLER GRASP
=================================================================

[Controller] Sessão de venda #VND-2026-0042 iniciada.
  🛒 [Controller] Item adicionado: 1x Monitor Ultrawide 34'
  🛒 [Controller] Item adicionado: 2x Teclado Mecânico Ergonômico
  ❌ [Controller] Erro: Produto com código 'CODIGO_INVALIDO' não cadastrado.
  🏷️ [Controller] Cupom de R$ 100.00 aplicado à venda.
  💾 [Repositório] Venda #VND-2026-0042 persistida com sucesso no banco de dados.
  ✅ [Controller] Venda finalizada com êxito! Total: R$ 4105.00