Módulo 10: Paradigmas de Programação & Padrões de Projeto • Bloco 2: SOLID e GRASP

🏛️ A Tríade Fundamental do Design Orientado a Objetos: Os cinco princípios SOLID, consolidados por Robert C. Martin (“Uncle Bob”) no início dos anos 2000, representam as fundações da arquitetura de software sustentável. Neste capítulo, exploramos a fundo os três primeiros pilares: a Responsabilidade Única (SRP), a Extensibilidade Fechada (OCP) e o Contrato de Substituição Formal de Liskov (LSP).


🗺️ Mapa Conceitual do Capítulo

flowchart TD
    SOLID["SOLID (Parte I: Fundamentos Estruturais)"]
    
    SOLID --> SRP["1. Single Responsibility Principle (SRP)<br/>• Um módulo deve ter apenas uma razão para mudar<br/>• Separação por Atores de Negócio (CEO, CFO, CTO)<br/>• Eliminação do Anti-Padrão God Class"]
    
    SOLID --> OCP["2. Open / Closed Principle (OCP)<br/>• Aberto para Extensão, Fechado para Modificação<br/>• Polimorfismo e Inversão de Controle<br/>• Extensões via Plugins sem tocar no core"]
    
    SOLID --> LSP["3. Liskov Substitution Principle (LSP)<br/>• Subtipos devem honrar os contratos dos supertipos<br/>• Pré-condições não fortalecidas / Pós não enfraquecidas<br/>• Invariantes preservadas & Eliminação de instanceof"]

    style SOLID fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style SRP fill:#e8f5e9,stroke:#4caf50,stroke-width:1.5px
    style OCP fill:#fff3e0,stroke:#ff9800,stroke-width:1.5px
    style LSP fill:#fce4ec,stroke:#e91e63,stroke-width:1.5px

🏛️ 1. Gênese Teórica e a Filosofia do SOLID

No final da década de 1990 e início dos anos 2000, Robert C. Martin reuniu e sistematizou princípios fundamentais formulados por pioneiros da ciência da computação (como Bertrand Meyer e Barbara Liskov) sob o acrônimo mnemônico SOLID:

  • S – Single Responsibility Principle (SRP)
  • O – Open/Closed Principle (OCP)
  • L – Liskov Substitution Principle (LSP)
  • I – Interface Segregation Principle (ISP)
  • D – Dependency Inversion Principle (DIP)

O objetivo primário desses princípios é conter o que Martin define como as quatro patologias degenerativas do design de software:

  1. Rigidez (Rigidity): A tendência do software de ser difícil de alterar, pois qualquer mudança gera uma cascata de alterações em módulos dependentes.
  2. Fragilidade (Fragility): A tendência do software de quebrar em lugares não relacionados sempre que uma alteração é introduzida.
  3. Imobilidade (Immobility): A incapacidade de reutilizar componentes em outros sistemas porque eles estão fortemente amarrados ao contexto original.
  4. Viscosidade (Viscosity): Quando fazer a coisa certa pelo design é mais difícil do que recorrer a atalhos e “gambiarras” (hacks).

🎯 2. Princípio da Responsabilidade Única (SRP)

“A class should have one, and only one, reason to change.” — Robert C. Martin

2.1 A Reinterpretação Moderna: Um Módulo para um Ator

A frase clássica costuma ser mal interpretada por iniciantes, que tentam decompor classes até que elas tenham apenas um único método. Martin esclareceu a definição formal:

  • Um módulo ou classe deve ser responsável perante um, e apenas um, ator de negócio.
  • Um ator é um grupo de usuários, stakeholders ou departamentos da empresa que demandam alterações no sistema (por exemplo: Contabilidade, Recursos Humanos, Operações, Marketing).
   Anti-Padrão: Classe Centralizada que atende múltiplos Atores
   
   ┌──────────────────────────────────────────────┐
   │                  Funcionario                 │
   ├──────────────────────────────────────────────┤
   │ + calcularSalarioLiquido()   [Ator: CFO / RH]│ ◄── Alteração no cálculo
   │ + emitirRelatorioHoras()     [Ator: COO / Op]│     pode quebrar o relatório
   │ + salvarNoBancoDeDados()     [Ator: CTO / TI]│     ou a persistência!
   └──────────────────────────────────────────────┘

Se a equipe contábil solicita uma alteração na fórmula de cálculo de horas extras e o desenvolvedor modifica a classe Funcionario, ele corre o risco inaceitável de introduzir regressões no relatório operacional de turnos ou no layout da folha de pagamento.

2.2 Sinais de Violação e Métricas de Coesão

  • Anti-Padrão God Class (Blob): Classes que acumulam milhares de linhas de código, dezenas de imports não relacionados e realizam validação, persistência SQL, geração de PDF e chamadas de API externa.
  • Métrica LCOM (Lack of Cohesion of Methods): Mede o compartilhamento de atributos entre métodos de uma classe. Se os métodos de uma classe operam sobre subconjuntos completamente disjuntos de campos, a classe possui alta falta de coesão e deve ser dividida em classes separadas.
  • Conflitos Frequentes de Git Merge: Quando desenvolvedores de times diferentes alteram o mesmo arquivo .java ou .py rotineiramente na mesma sprint, a classe é um ímã de responsabilidades cruzadas.

🔓 3. Princípio Aberto / Fechado (OCP)

“Software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.” — Bertrand Meyer (1988)

3.1 Meyer vs. Martin: Da Herança ao Polimorfismo

  • OCP de Bertrand Meyer (1988): Focado em Eiffel e herança clássica. Uma vez que um módulo é compilado e homologado, seu código-fonte é considerado fechado; extensões ocorrem criando subclasses que herdam do módulo base.
  • OCP Polimórfico de Martin (Anos 2000): Focado em interfaces e inversão de controle. O módulo fecha-se contra alterações através de abstrações (interfaces/contratos puros), permitindo que novos comportamentos sejam injetados via novas implementações de plugins sem que nenhuma linha do módulo consumidor precise ser reescrita ou recompilada.
flowchart TD
    CLIENT["ProcessadorDePagamento<br/>(FECHADO para modificação)"]
    
    CLIENT --> INTF["«interface»<br/>MetodoPagamento<br/>+ processar(valor)"]
    
    INTF -.-> P1["PixPagamento<br/>(Extensão Nova)"]
    INTF -.-> P2["CartaoCredito<br/>(Extensão Nova)"]
    INTF -.-> P3["BoletoBancario<br/>(Extensão Nova)"]
    INTF -.-> P4["CriptoMoeda<br/>(Extensão Nova)"]

    style CLIENT fill:#ffebee,stroke:#f44336,stroke-width:2px
    style INTF fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style P1 fill:#e8f5e9,stroke:#4caf50,stroke-width:1px
    style P2 fill:#e8f5e9,stroke:#4caf50,stroke-width:1px
    style P3 fill:#e8f5e9,stroke:#4caf50,stroke-width:1px
    style P4 fill:#e8f5e9,stroke:#4caf50,stroke-width:1px

3.2 O Perigo dos Laços switch / if-else Infinitos

O sintoma clássico de violação do OCP é a presença recorrente de blocos condicionais inspecionando enums ou tipos literais:

# VIOLAÇÃO DO OCP: Toda nova operadora exige alterar este método já testado!
def calcular_frete(tipo_transportadora: str, peso: float) -> float:
    if tipo_transportadora == "CORREIOS_PAC":
        return peso * 12.5 + 10.0
    elif tipo_transportadora == "CORREIOS_SEDEX":
        return peso * 22.0 + 25.0
    elif tipo_transportadora == "LOGGI_EXPRESS":
        return peso * 18.0 + 15.0
    # Adicionar FEDEX aqui exige alterar código em produção!

Ao refatorar para o OCP com uma interface comum CalculadoraFrete, novas operadoras tornam-se classes autônomas plugadas em tempo de execução, garantindo risco zero de quebra para os algoritmos já validados.


🔄 4. Princípio de Substituição de Liskov (LSP)

“Let $\phi(x)$ be a property provable about objects $x$ of type $T$. Then $\phi(y)$ should be true for objects $y$ of type $S$ where $S$ is a subtype of $T$.” — Barbara Liskov (1987)

O Princípio de Substituição de Liskov estabelece as condições formais de corretude semântica para o subtipo. Uma subclasse não pode apenas ter a mesma assinatura sintática da superclasse; ela deve cumprir rigorosamente o seu contrato comportamental.

4.1 As Regras Formais do Design by Contract (DbC)

Para que $S$ seja um substituto lícito de $T$:

  1. Pré-condições Não Podem Ser Fortalecidas: Uma subclasse não pode exigir mais do que a classe base exigia (ex.: se a classe base aceita qualquer número inteiro, a subclasse não pode exigir apenas inteiros positivos).
  2. Pós-condições Não Podem Ser Enfraquecidas: A subclasse não pode garantir menos do que a superclasse prometia retornar (ex.: se a superclasse garantia devolver uma lista ordenada não nula, a subclasse não pode devolver null ou dados desordenados).
  3. Invariantes Devem Ser Preservadas: Todas as regras estáticas de integridade mantidas pela superclasse devem permanecer intactas na subclasse.
  4. Restrição de Histórico (History Constraint): Uma subclasse não pode permitir mutações de estado que a superclasse definia como imutáveis.

4.2 O Dilema Clássico do Quadrado e Retângulo

Na matemática euclidiana, “todo quadrado é um retângulo”. No entanto, na modelagem orientada a objetos com estado mutável, essa relação de herança viola catastroficamente o LSP:

class Retangulo:
    def __init__(self, largura: float, altura: float):
        self._largura = largura
        self._altura = altura

    def set_largura(self, l: float): self._largura = l
    def set_altura(self, a: float): self._altura = a
    def calcular_area(self) -> float: return self._largura * self._altura

class Quadrado(Retangulo):
    # Violação do LSP: ao alterar um lado, altera o outro forçadamente
    def set_largura(self, l: float):
        self._largura = l
        self._altura = l

    def set_altura(self, a: float):
        self._largura = a
        self._altura = a

Se uma função cliente espera um Retangulo e testa a invariante:

def redimensionar(r: Retangulo):
    r.set_largura(5)
    r.set_altura(4)
    assert r.calcular_area() == 20, "Erro: área esperada é 20!"

Ao passar um objeto Quadrado, o assert explode com falha (calcular_area() resultará em 16), provando que o subtipo quebrou o comportamento esperado da superclasse.

4.3 O Anti-Padrão da Ave que Não Voa e Exceções Abstratas

Outra manifestação grave é criar subclasses que herdam métodos aos quais não respondem conceitualmente, sobrepondo-os com exceções:

class Ave:
    def voar(self): pass

class Pinguim(Ave):
    def voar(self):
        raise NotImplementedError("Pinguins não voam!") # Quebra o LSP!

Qualquer código que iterar sobre uma lista polimórfica List[Ave] e invocar .voar() colapsará em tempo de execução com exceção não tratada. A solução arquitetural correta é segregar as interfaces (Ave genérica vs. AveVoadora).


📊 5. Matriz Comparativa: SRP, OCP e LSP

Princípio SOLID Pergunta Central de Auditoria Anti-Padrão Típico Técnica Corretiva Principal
SRP (Responsabilidade Única) Quantos atores de negócio distintos podem exigir alterações neste arquivo? God Class / Blob, arquivos gigantescos com lógica mista de UI, DB e negócio Decomposição em classes de domínio coesas e serviços orquestradores
OCP (Aberto / Fechado) Para adicionar uma nova variação comercial, preciso alterar código já testado? Blocos massivos de switch / if-else inspecionando enums de tipo Criação de abstrações/interfaces e injeção de dependência polimórfica
LSP (Substituição de Liskov) Posso substituir a classe base pela subclasse em qualquer chamada sem quebras? Métodos sobrepostos com raise NotImplementedError, checagens com isinstance Segregação de hierarquias de herança e respeito estrito ao Design by Contract