O Colapso da Herança e a Era da Composição: o Padrão Strategy

Aula 10 — Programação Orientada a Objetos

Marcos M. Raimundo — Instituto de Computação, UNICAMP

2026-09-18

Proposta da Aula

Herança (Aulas 8–9) modelou muito bem hierarquias com um eixo de variação — Pix, Cartão, Boleto como especializações de um único tipo base.

Mas o que acontece quando o negócio varia em dois eixos independentes ao mesmo tempo — físico/digital e, separadamente, nacional/importado?

Roteiro: a fusão estrutural e seu custo → o colapso da hierarquia (explosão combinatória) → a era da composição → o padrão Strategy.

A Fusão Estrutural e o Peso da Herança de Estado

extends: a Fusão que Você Não Pediu

class B extends A não é só “B usa métodos de A” — é fundir as estruturas de ambas numa única entidade lógica, o mesmo “DNA”.

A promessa: onde o sistema espera um A, deve aceitar um B sem perceber diferença.

A Ilusão da Produtividade

public class PedidoInternacional extends Pedido {
    // "Ganhei" os metodos calcularTotal() e getItens() de graca!
    public double calcularTaxaAlfandega() { /* ... */ }
}

Se Pedido passar a exigir um endereço de faturamento nacional no construtor, PedidoInternacional quebra — ninguém errou o código, a herança forçou uma identidade que não existia.

Trocamos duplicação (barata de corrigir depois) por acoplamento estrutural (caro de desfazer).

O Vínculo de Sangue: Acoplamento Estrutural

O mais rígido dos vínculos em OO: acesso às “entranhas” via protected, comportamento fixado em tempo de compilação.

Não é possível trocar de “pai” em tempo de execução.

Herança de Estado vs. Comportamento

Comportamento (interfaces): fluida — o filho promete responder a um contrato, soberano sobre o como.

Estado (classes): rígida — o filho herda campos físicos de memória; o layout do pai é imposto.

O Peso na Memória: a “Subclasse Gorda”

public abstract class ProdutoBase {
    protected double pesoFisico;      // essencial para logistica e frete
    protected String dimensoesPacote; // essencial para envio fisico
}

public class Ebook extends ProdutoBase {
    // Ebook herda pesoFisico e dimensoesPacote na Heap, mesmo sem usar!
}

Milhares de Ebook = milhares de bytes desperdiçados em atributos irrelevantes para um produto digital.

Diferente da Aula 8 (Classe Base Frágil, base que muda): aqui a base nem precisa mudar — o custo já existe, silencioso, desde a primeira instância.

O Colapso da Hierarquia: Explosão Combinatória

O Cenário: a Classe Base Limpa

public abstract class Produto {
    protected String id;
    protected double precoBase;
    public abstract double calcularPrecoFinal();
}

Início perfeitamente razoável — um contrato único de precificação para todo o catálogo.

A Primeira Especialização: Logística

public class ProdutoFisico extends Produto {
    protected double pesoGrama;
    @Override
    public double calcularPrecoFinal() { return precoBase + calcularFrete(); }
}

public class ProdutoDigital extends Produto {
    protected String linkDownload;
    @Override
    public double calcularPrecoFinal() { return precoBase; }
}

Armadilha invisível: “ser físico” vira propriedade primária da identidade, quando é só uma faceta do ciclo de vida.

O Choque de Dimensões: Fiscalidade

Novo requisito: Nacional (isenção) vs. Importado (taxa alfandegária).

Inserir em Produto: polui a base para quem não precisa. Criar ProdutoImportado: choca com Fisico/Digital já estabelecido.

O Colapso: a Matriz de Explosão

4 classes hoje, 8 se adicionarmos um terceiro eixo binário — e a regra fiscal duplicada entre FisicoImportado/DigitalImportado não tem fonte única de verdade.

O Limite da Linguagem: o Diamante

// Esta abordagem e sintaticamente invalida em Java
public class FisicoImportado extends ProdutoFisico, ProdutoImportado {
    // Erro de Compilacao
}

Java proíbe para evitar o Problema do Diamante: se ambas as superclasses declarassem um campo com o mesmo nome, qual prevaleceria?

O Diagnóstico

Herança de classes: feita para taxonomias biológicas e imutáveis (todo Cachorro é um Mamífero).

Logística e fiscalidade não são identidade — são features modulares que variam de forma independente.

Herança fracassa catastroficamente ao tentar agrupar comportamentos ortogonais entre si.

O Colapso da Hierarquia e a Explosão Combinatória

Se hoje o e-commerce adicionasse um terceiro eixo de variação (Perecível vs. Não-Perecível) ainda usando herança pura, quantas classes-folha existiriam — e por que dobrar de 4 para 8 não é o pior problema dessa abordagem?

Escreva sua resposta e compare com um colega antes de avançar (2 min).

  • □ Em uma hierarquia de herança pura que já modela dois eixos binários independentes com 4 subclasses-folha, a adição de um terceiro eixo binário eleva esse número para 8, mantendo a mesma lógica de crescimento.
  • □ Se Java permitisse herança múltipla de classes sem nenhuma outra mudança de linguagem, o problema de duplicação de lógica fiscal entre FisicoImportado e DigitalImportado desapareceria completamente, sem risco de ambiguidade.
  • □ Um sistema de biblioteca que precisa classificar itens simultaneamente por Formato (Físico/Digital) e por Categoria de Empréstimo (Curto Prazo/Longo Prazo) enfrentaria o mesmo padrão de explosão combinatória se modelado por herança de classes pura.
  • □ Herança de classes é uma ferramenta inadequada para qualquer sistema que apresente mais de uma característica variável, mesmo quando essas características não são independentes entre si (por exemplo, quando uma determina totalmente a outra).

O Colapso da Hierarquia — Resposta

Explosão combinatória — Resposta

  • ✔ Em uma hierarquia de herança pura que já modela dois eixos binários independentes com 4 subclasses-folha, a adição de um terceiro eixo binário eleva esse número para 8, mantendo a mesma lógica de crescimento — o crescimento é multiplicativo (2×2×2), não aditivo.
  • ✗ Se Java permitisse herança múltipla de classes sem nenhuma outra mudança de linguagem, o problema de duplicação de lógica fiscal entre FisicoImportado e DigitalImportado desapareceria completamente, sem risco de ambiguidade — herança múltipla resolveria a duplicação de código, mas introduziria exatamente o Problema do Diamante (ambiguidade de estado/método) que o diagrama ilustra.
  • ✔ Um sistema de biblioteca que precisa classificar itens simultaneamente por Formato e por Categoria de Empréstimo enfrentaria o mesmo padrão de explosão combinatória se modelado por herança de classes pura — dois eixos independentes, mesmo problema estrutural, domínio diferente.
  • ✗ Herança de classes é uma ferramenta inadequada para qualquer sistema que apresente mais de uma característica variável, mesmo quando essas características não são independentes entre si — a explosão só ocorre quando os eixos são ortogonais; se uma característica determina totalmente a outra, não há combinações genuinamente novas a cobrir.

Voltando à pergunta: o terceiro eixo levaria a 8 classes, mas o pior problema não é o número — é que a regra fiscal ficaria duplicada em FisicoImportado e DigitalImportado (e depois em mais 2 folhas), sem uma única fonte de verdade. Uma mudança na lei fiscal exigiria editar múltiplos arquivos, com alto risco de esquecer um deles.

A Era da Composição: Princípio, Is-a para Has-a e Quase-Decomponibilidade

A Regra de Ouro do GoF e Três Razões Estruturais

“Favoreça a composição de objetos sobre a herança de classes.”

Não é estilo — é mitigação de risco estrutural, por três razões:

Encapsulamento preservado: composição fala com interfaces públicas; herança expõe protected.

Flexibilidade dinâmica: composição monta/altera comportamentos em tempo de execução; herança fixa antes do programa iniciar.

Desacoplamento de eixos: Tributacao e Logistica como colaboradores independentes — mudar um não afeta o outro.

Mudança de Perspectiva: de Is-a para Has-a

Is-a: identidade estrita, o subtipo carrega toda a história física do pai.

Has-a: o objeto é um orquestrador, delega trabalho pesado a especialistas.

Escolha: herança para especialização essencial; composição para consumo de serviço.

A Arquitetura da Complexidade (Herbert Simon)

Sistemas estáveis são hierarquias de subsistemas simples — quase-decomponibilidade: elos internos fortes, dependências externas fracas.

Sistemas que funcionam evoluíram de sistemas simples que já funcionavam, combinando peças padronizadas.

Sistemas Modulares vs. Integrados

Integração (herança): TV com VCR embutido — a engrenagem do vídeo quebra, a TV inteira vai para o conserto.

Modularidade (composição): som modular — o CD Player queima, você troca só ele; caixas e amplificador seguem operacionais.

No e-commerce: API dos Correios muda? Trocamos só a classe de logística, Pedido intocado.

Tipos de Composição e Fronteiras de Domínio

Agregação: Todo-Parte

O “Todo” agrupa, mas não é dono absoluto das vidas das partes.

CarrinhoDeCompras contém Produtos — deletar o carrinho não deleta os produtos do estoque.

Associação: Colaboração Livre

Entidades completamente autônomas: Cliente associado a CartaoDeCredito.

O cartão pode existir sem aquele cliente; o cliente não depende daquele cartão específico.

Independência e Testabilidade

Composição desmembra mil linhas em dezenas de classes isoladas e coesas.

Regra tributária testável isoladamente, sem instanciar o grafo de ProdutoFisico.

Equipes diferentes trabalham em frete e impostos em paralelo, sem conflitos de versão.

Evitando a Mistura de Domínios

Tentação: juntar comunicação, infraestrutura e negócio na mesma classe.

Especialista da Informação (revisitado da Aula 5): a responsabilidade recai sobre quem tem os dados.

Produto gerencia nome/preço — não conecta a SMTP, não gera PDF.

Composição, Agregação e Associação

Por que dizer que o Carrinho “tem” Produtos (Agregação) e o Cliente “usa” um Cartão de Crédito (Associação) leva a decisões de código diferentes sobre quem cria e quem destrói cada objeto?

Escreva sua resposta e compare com um colega antes de avançar (2 min).

  • □ Se todos os Produtos de um Carrinho fossem deletados do sistema no exato instante em que o Carrinho é deletado, essa relação deixaria de ser uma Agregação clássica e passaria a se comportar como Composição estrita.
  • □ Se o Cliente fosse implementado de forma que a existência do CartaoDeCredito dependesse inteiramente do Cliente (o cartão nunca poderia existir sem aquele cliente específico), essa relação ainda seria corretamente descrita como Associação livre.
  • □ Uma classe Playlist que agrupa referências a Musica, mas cujas músicas continuam existindo na biblioteca mesmo depois que a Playlist é apagada, exemplifica o mesmo padrão de Agregação visto no Carrinho de Compras.
  • □ Aplicar o princípio do Especialista da Informação significa que toda funcionalidade relacionada, de qualquer forma, a um objeto deve ser implementada dentro dele, mesmo que envolva conectar-se a servidores externos.

Composição, Agregação e Associação — Resposta

Quem cria, quem destrói — Resposta

  • ✔ Se todos os Produtos de um Carrinho fossem deletados do sistema no exato instante em que o Carrinho é deletado, essa relação deixaria de ser uma Agregação clássica e passaria a se comportar como Composição estrita — o critério que separa as duas é exatamente a dependência (ou não) de ciclo de vida.
  • ✗ Se o Cliente fosse implementado de forma que a existência do CartaoDeCredito dependesse inteiramente do Cliente, essa relação ainda seria corretamente descrita como Associação livre — Associação pressupõe vidas completamente autônomas; uma dependência de ciclo de vida nesse sentido já descaracteriza a Associação.
  • ✔ Uma classe Playlist que agrupa Musica, cujas músicas sobrevivem à exclusão da Playlist, exemplifica o mesmo padrão de Agregação visto no Carrinho — mesmo critério (Todo-Parte sem posse absoluta da vida), domínio diferente.
  • ✗ Aplicar o Especialista da Informação significa que toda funcionalidade relacionada a um objeto deve ser implementada dentro dele, mesmo envolvendo servidores externos — é exatamente o oposto: o princípio existe para isolar infraestrutura das classes de domínio puro.

Voltando à pergunta: Agregação e Associação diferem em quem é responsável pelo ciclo de vida da parte. Isso muda o código diretamente — quem chama new, quem chama a limpeza/remoção, e se um objeto pode sobreviver sozinho depois que seu colaborador desaparece.

O Desafio do Checkout: do Labirinto de Condicionais à Herança Fracassada

O Novo Requisito: Variação no Checkout

Pix, Cartão, Boleto — cada um com regra de cálculo, comunicação externa e tempo de processamento próprios.

Como acoplar essa variação a Pedido sem explodir a complexidade?

A Abordagem Ingênua: o Labirinto do if/else

public void processarPagamento(String tipo) {
    if (tipo.equals("PIX")) {
        // QR Code + desconto de 5%
    } else if (tipo.equals("CARTAO")) {
        // antifraude + gateway
    } else if (tipo.equals("BOLETO")) {
        // codigo de barras + vencimento
    }
}

Cresce indefinidamente; Pedido conhece detalhes de infraestrutura alheios; mudar o Pix arrisca quebrar o Cartão.

A Tentativa por Herança: o Colapso da Identidade

// Tentando resolver variacoes criando subtipos
public class PedidoPix extends Pedido { /* ... */ }
public class PedidoCartao extends Pedido { /* ... */ }

Erro semântico: confunde identidade (o que Pedido é) com papel (o que ele usa temporariamente).

Aprisionamento estático: identidade fixada em new — trocar Pix por Cartão no último segundo exige recriar o objeto inteiro.

Explosão da árvore: reabre o mesmo colapso combinatório do bloco anterior, agora com “meio de pagamento” como novo eixo.

A pergunta certa deixa de ser “o que este Pedido é?” e passa a ser “qual estratégia de pagamento este Pedido usa?”

O Padrão Strategy: Composição, Delegação e o Princípio Aberto/Fechado

Desmembrando a Lógica: Definindo a Fronteira do Contrato

Pedido deixa de processar pagamentos; delega a um especialista financeiro.

Extraímos os blocos do if/else em classes independentes e coesas.

// O contrato que define o que uma estrategia de pagamento deve fazer
public interface Pagavel {
    boolean processarPagamento(double valorBase);
}

public class PagamentoPix implements Pagavel { /* ... */ }
public class PagamentoCartao implements Pagavel { /* ... */ }

Pedido conversa só com Pagavel — ignorância intencional sobre o “como”.

Mesmos nomes das Aulas 8–9 (Pagavel, Pix, Cartao, Boleto) — lá eram subclasses; aqui, implementações independentes injetadas por composição.

Injetando a Solução: Flexibilidade Dinâmica

public class Pedido {
    private Pagavel estrategiaPagamento;
    private double total;

    // A estrategia e injetada em tempo de execucao
    public void setEstrategiaPagamento(Pagavel pagamento) {
        this.estrategiaPagamento = pagamento;
    }
}

O set resolve o aprisionamento estático: trocar Pix por Cartão no checkout sem destruir o Pedido.

Execução via Delegação

public void finalizarPedido() {
    if (this.estrategiaPagamento == null) {
        throw new IllegalStateException("Estrategia de pagamento ausente.");
    }
    // Delegacao: o Pedido pede ao especialista que execute a tarefa pesada
    boolean sucesso = this.estrategiaPagamento.processarPagamento(this.total);
}

Associação (conhecer) + Delegação (usar) = fim dos if/switch de roteamento.

O Padrão Strategy: Topologia GoF

Context mantém referência (has-a) à Strategy; ConcreteStrategies implementam o mesmo contrato.

Late Binding revisitado: a VTable do objeto real na Heap decide, em tempo de execução, qual processarPagamento roda.

O OCP em Ação: um Novo Meio de Pagamento

// Novo requisito: pagamento em criptomoedas
public class PagamentoCriptomoeda implements Pagavel {
    public boolean processarPagamento(double valor) {
        return true; // logica de interacao com a blockchain
    }
}

Aberto para extensão: nova classe, sem tocar em nada existente.

Fechado para modificação: Pedido não muda, não recompila, não arrisca quebrar o que já funciona.

O Padrão Strategy e o Princípio Aberto/Fechado

Se amanhã a loja quiser aceitar pagamento via Criptomoeda, por que a resposta certa nunca deveria envolver abrir e editar o arquivo Pedido.java?

Escreva sua resposta e compare com um colega antes de avançar (2 min).

  • □ Se o Contexto (Pedido) precisasse verificar, com um if, se a estratégia injetada é uma instância de Pix antes de chamar processarPagamento(), isso indicaria que o padrão Strategy não foi implementado corretamente.
  • □ Se a interface Pagavel não existisse e o Pedido dependesse diretamente da classe concreta Pix, ainda seria possível trocar a estratégia de pagamento em tempo de execução sem modificar o código de Pedido.
  • □ Um sistema de notificações que aceita diferentes canais (EmailNotificador, SmsNotificador, PushNotificador) implementando uma interface comum Notificador, injetada no objeto que dispara os alertas, segue a mesma topologia Context/Strategy/ConcreteStrategy vista no pagamento.
  • □ O Princípio Aberto/Fechado exige que uma classe nunca seja modificada depois de escrita, mesmo para corrigir um bug real dentro de sua própria lógica interna.

O Padrão Strategy e o OCP — Resposta

Por que não editar Pedido.java — Resposta

  • ✔ Se o Contexto precisasse verificar, com um if, se a estratégia é uma instância de Pix antes de chamar processarPagamento(), isso indicaria que o Strategy não foi implementado corretamente — o Contexto deveria confiar cegamente no contrato, nunca inspecionar o tipo concreto.
  • ✗ Se Pagavel não existisse e Pedido dependesse diretamente de Pix, ainda seria possível trocar a estratégia em tempo de execução sem modificar Pedido — sem a interface, Pedido estaria acoplado à classe concreta, e qualquer novo meio de pagamento exigiria editar Pedido para conhecer a classe nova.
  • ✔ Um sistema de notificações com EmailNotificador/SmsNotificador/PushNotificador implementando Notificador, injetada no objeto que dispara alertas, segue a mesma topologia Context/Strategy/ConcreteStrategy — mesmo padrão estrutural, domínio diferente.
  • ✗ O OCP exige que uma classe nunca seja modificada depois de escrita, mesmo para corrigir um bug real em sua própria lógica interna — o OCP é sobre extensão de comportamento variável (novos meios de pagamento), não uma proibição absoluta de qualquer edição; corrigir um bug na orquestração de Pedido é manutenção legítima, diferente de alterá-lo toda vez que um novo meio de pagamento surge.

Voltando à pergunta: porque Pedido só conhece a abstração Pagavel. Uma nova classe PagamentoCriptomoeda implementando esse contrato basta — nenhuma linha de Pedido.java precisa mudar, e o sistema não corre o risco de quebrar Pix, Cartão ou Boleto no processo.

Conclusão

O que Aprendemos

  1. A fusão estrutural do extends tem custo: acoplamento rígido, peso físico na Heap
  2. Eixos independentes de variação colapsam a herança pura em explosão combinatória — e Java fecha até a saída de herança múltipla (Diamante)
  3. Composição (has-a) troca identidade fixa por orquestração dinâmica — Agregação, Associação, Especialista da Informação
  4. O padrão Strategy resolve o labirinto de if/else com uma interface, injeção de dependência e delegação — o OCP na prática

Próxima aula: o vocabulário geral de Padrões de Projeto — origem histórica, o marco GoF, e as três famílias (Criação/Estrutural/Comportamento) vistas de cima.

Exercícios Soluções