Especialização em Sistemas com Python & POO com Type Hints • Capítulo 09


🗺️ Mapa Conceitual do Tópico

flowchart TD
    subgraph INHERITANCE["Herança e Polimorfismo Nominal"]
        LSP["Princípio de Substituição de Liskov (SOLID LSP)\nSubclasses Devem Honrar os Contratos da Superclasse"]
        ABC_MOD["Módulo abc (Abstract Base Classes)\nMetaclasse ABCMeta · @abstractmethod · @abstractproperty"]
    end

    subgraph MULTIPLE["Herança Múltipla e Resolução Cooperativa"]
        DIAMOND["Problema do Diamante (Ambiguidade Estrutural)"]
        C3["Algoritmo C3 Linearization\nOrdem Determinística & Consistência Monotônica"]
        MRO["Method Resolution Order (Classe.mro())\nCadeia de Invocação Cooperativa via super()"]
    end

    subgraph PATTERNS["Padrões Arquiteturais OO"]
        TEMPLATE["Template Method Pattern\nEsqueleto Abstrato com Hooks Concretos"]
        MIXIN["Classes Mixin Ortogonais\nExtensão de Capacidades Sem Poluir a Hierarquia"]
    end

    INHERITANCE --> MULTIPLE
    MULTIPLE --> MRO
    ABC_MOD --> TEMPLATE
    MULTIPLE --> MIXIN

    style INHERITANCE fill:#ede7f6,stroke:#512da8,stroke-width:2px
    style MULTIPLE fill:#e1f5fe,stroke:#0288d1,stroke-width:2px
    style PATTERNS fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px

🧬 1. Classes Abstratas Formais (abc.ABC) e Contratos Rígidos

Em Python, a herança nominal pura permite compartilhar código e estabelecer uma relação semântica do tipo “É-Um” (IS-A). Para prevenir que classes base incompletas sejam instanciadas no sistema, a biblioteca padrão provê o módulo abc (Abstract Base Classes):

  1. Metaclasse ABCMeta (ou herança direta de abc.ABC): Intercepta o método __new__ durante a instanciação. Se a classe ou subclasse contiver qualquer método decorado com @abstractmethod não implementado, o interpretador aborta com TypeError: Can't instantiate abstract class ... with abstract method.
  2. Métodos Abstratos com Corpo: Diferente de interfaces em C++ ou Java clássico, métodos abstratos em Python podem conter código compartilhado. Subclasses podem sobrescrever o método e chamar a lógica comum através de super().metodo().
  3. Propriedades Abstratas: Combinam @property e @abstractmethod para exigir que a subclasse implemente getters ou setters específicos.
from abc import ABC, abstractmethod
from decimal import Decimal

class ProcessadorPagamento(ABC):
    @property
    @abstractmethod
    def taxa_administrativa(self) -> Decimal:
        """Propriedade abstrata obrigatória em todas as subclasses."""
        ...

    @abstractmethod
    def liquidar(self, valor: Decimal) -> bool:
        """Método abstrato de liquidação."""
        ...

💎 2. O Problema do Diamante e o Algoritmo C3 Linearization (MRO)

Em linguagens com suporte a Herança Múltipla, surge o clássico dilema do diamante: se a classe D herda de B e C, e ambas herdam de A, qual versão de um método sobrescrito D deve executar?

classDiagram
    direction TB
    class A {
        +executar()
    }
    class B {
        +executar()
    }
    class C {
        +executar()
    }
    class D {
        +executar()
    }
    A <|-- B
    A <|-- C
    B <|-- D
    C <|-- D

O Algoritmo C3 Linearization

Desde o Python 2.3 e mantido em todas as versões do Python 3, a ordem de busca é resolvida deterministicamente pelo algoritmo C3 Linearization, garantindo três propriedades fundamentais:

  1. Preservação da Precedência Local: Se na declaração consta class D(B, C):, os métodos de B sempre serão avaliados antes dos de C.
  2. Monotonicidade: Se uma classe precede outra na hierarquia de uma superclasse, essa ordem relativa é mantida em todas as subclasses derivadas.
  3. Ausência de Ciclos: O compilador rejeita hierarquias inconsistentes em tempo de definição.

A Função super() e Chamadas Cooperativas

Em Python, super() NÃO chama o pai direto, mas sim o próximo elemento na lista linearizada do MRO:

class D(B, C):
    def executar(self) -> None:
        super().executar() # Consulta D.mro() para saber quem chamar!

# Inspecionando o MRO no terminal:
print([cls.__name__ for cls in D.mro()])
# Saída: ['D', 'B', 'C', 'A', 'object']

🧩 3. Padrão Mixin: Reuso Transversal sem Acoplamento

Um Mixin é uma classe projetada para fornecer funcionalidades pontuais e reutilizáveis a outras classes via herança múltipla, respeitando três regras canônicas de design:

  • Não deve ser instanciada diretamente.
  • Não deve possuir estado de instância próprio (não define __init__ próprio ou utiliza super().__init__).
  • Não herda de classes de domínio; atua como um modificador de capacidade.
import json
from datetime import datetime, timezone

class AuditavelMixin:
    """Mixin que confere capacidade de auditoria e carimbo temporal a qualquer entidade."""
    def obter_metadados_auditoria(self) -> dict[str, str]:
        return {
            "classe": self.__class__.__name__,
            "id_memoria": hex(id(self)),
            "timestamp_utc": datetime.now(timezone.utc).isoformat()
        }

class SerializavelJSONMixin:
    """Mixin para exportação rápida de dicionários em formato JSON."""
    def to_json(self) -> str:
        return json.dumps(getattr(self, "__dict__", {}), default=str)

🏛️ 4. Template Method Pattern

O padrão Template Method da GoF define o esqueleto de um algoritmo na superclasse abstrata, delegando passos específicos para as subclasses concretas:

sequenceDiagram
    participant Cliente
    participant Pipeline as PipelineExportacao (Superclasse)
    participant CSV as ExportadorCSV (Subclasse)

    Cliente->>Pipeline: executar_exportacao()
    Pipeline->>Pipeline: autenticar_requisicao()
    Pipeline->>CSV: extrair_registros()
    Pipeline->>CSV: formatar_linhas()
    Pipeline->>Pipeline: registrar_log_auditoria()
    Pipeline-->>Cliente: Arquivo Final Gerado

Subclasses customizam a extração e a formatação sem quebrar o fluxo coordenado de segurança e auditoria da superclasse.