Composição de Sistemas: Contratos e Estabilidade

Aula 3 — Programação Orientada a Objetos

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

2026-08-26

Proposta da Aula

Até aqui: anatomia de uma peça isolada (Aulas 1–2).

A partir de agora: como as peças se conectam — a Orientação a Objetos como design de comunicações.

Roteiro: colaboração → interface vs. implementação → Lei de Demeter → Plug-and-Play.

Especialistas Colaborando: Produto, ItemCarrinho, Carrinho

Cada Classe, um Especialista

public class Produto {
    private double precoBase;
    public double getPreco() { return this.precoBase; }
}

public class ItemCarrinho {
    private Produto produto;
    public double subtotal() { return produto.getPreco() * quantidade; } // pergunta, nao acessa
}

O Carrinho: Orquestrador, Não Calculador

public double calcularTotal() {
    double total = 0;
    for (ItemCarrinho item : itens) total += item.subtotal(); // delegacao
    return total;
}

Carrinho não sabe de preço nem imposto — só confia no contrato subtotal().

Dividir em especialistas evita Objetos Deus e permite testar cada peça isolada.

Interface vs. Implementação: a Fronteira da Estabilidade

O Cliente Confia na Caixa Preta

public class Compra {
    public void processar(CartaoDeCredito cartao) {
        ValidadorFinanceiro v = new ValidadorFinanceiro();
        if (v.isValido(cartao)) { /* ... */ } // nao pergunta COMO
    }
}

O “Como” Fica Escondido

public class ValidadorFinanceiro {
    public boolean isValido(CartaoDeCredito c) {         // O QUE (publico)
        return verificarLuhn(c) && consultarGateway(c);
    }
    private boolean verificarLuhn(CartaoDeCredito c) {...}   // COMO (privado)
    private boolean consultarGateway(CartaoDeCredito c) {...}
}

Trocar o gateway por REST ou gRPC: Compra nem percebe.

Encapsulamento e Evolução sem Regressão

V1: A Regra Simples

public double calcularPrecoDeVenda() {
    return this.precoCusto * 1.8;
}

V2: A Regra Ficou Complexa — Contrato Igual

public double calcularPrecoDeVenda() {
    if (this.precoCusto > 1000.0) return this.precoCusto * 1.75;
    return this.precoCusto * 1.90;
}

Mesma assinatura pública → zero impacto em quem chama calcularPrecoDeVenda().

Se Carrinho tivesse um getter para precoCusto e calculasse a margem por fora, o que exatamente quebraria quando a regra de margem mudasse?

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

Interface, Implementação e Evolução

Dica

Julgue V ou F — a questão só conta se acertar os 4 itens:

  1. A interface define o “O Quê” um objeto promete fazer; a implementação é o “Como”, escondido do cliente.
  2. Trocar a implementação interna de um método, mantendo sua assinatura, não deveria exigir mudanças no código cliente.
  3. ItemCarrinho.subtotal() deveria ler diretamente o atributo precoBase de Produto para ser mais eficiente.
  4. Encapsular calcularPrecoDeVenda() dentro de Produto centraliza a política de margem num único lugar do sistema.

Interface, Implementação e Evolução — Resposta

Dica

  1. Verdadeiro — é a definição central desta aula.
  2. Verdadeiro — é exatamente o benefício da estabilidade de contrato.
  3. Falso — isso violaria o encapsulamento; subtotal() deve perguntar via getPreco().
  4. Verdadeiro — é o mesmo argumento do Bloco 3, mudança de regra sem efeito cascata.

A Lei de Demeter

O Naufrágio de Código (Train Wreck)

double saldo = pedido.getCliente().getCarteira().getSaldo(); // violacao

Não é o número de pontos — é a fronteira invadida. Se Carteira mudar, Checkout quebra, mesmo sem relação direta com ela.

A Cura: Delegação em Cadeia

if (pedido.clientePodePagar()) { ... }               // Checkout fala so com Pedido

public boolean clientePodePagar() {                  // Pedido delega ao Cliente
    return this.cliente.temSaldoSuficiente(this.total);
}

Checkout fica cego para a existência de uma Carteira. Trocar por ApplePay: zero impacto.

Por que “número de pontos na linha” é um jeito ruim de explicar a Lei de Demeter, e “fronteira invadida” é melhor?

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

Lei de Demeter e Tell, Don’t Ask

Dica

Julgue V ou F — a questão só conta se acertar os 4 itens:

  1. A Lei de Demeter recomenda que um objeto interaja apenas com seus “amigos próximos”.
  2. pedido.getCliente().getCarteira().getSaldo() é um exemplo saudável de reuso de getters.
  3. Feature Envy é o sintoma de uma classe mais interessada nos dados de outra do que nos próprios.
  4. A cura técnica para o naufrágio de código é criar métodos de delegação, como pedido.clientePodePagar().

Lei de Demeter e Tell, Don’t Ask — Resposta

Dica

  1. Verdadeiro — é a própria definição da lei.
  2. Falso — é o exemplo clássico de violação (train wreck).
  3. Verdadeiro — é a definição do code smell.
  4. Verdadeiro — delegação em cadeia é exatamente a solução apresentada.

Programando para Abstrações: Sistemas Plug-and-Play

O Contrato Pagavel

public interface Pagavel { boolean isPagamentoValido(); }
public class Boleto implements Pagavel { ... }
public class CartaoDeCredito implements Pagavel { ... }

Checkout Aceita Qualquer Pagavel

public void finalizar(Pagavel metodo) {
    if (metodo.isPagamentoValido()) System.out.println("Transacao concluida!");
}

Amanhã surge o Pix → só Pix implements Pagavel. Checkout não muda.

O Erro de Tipagem como Diagnóstico

public void processar(CartaoDeCredito cartao) { ... }
processar(meuBoleto); // erro: "Boleto cannot be converted to Cartao"

O erro revela que o sistema “sabe demais”: exige identidade específica, não capacidade funcional.

Se Checkout.finalizar recebesse CartaoDeCredito em vez de Pagavel, o que exatamente o erro de compilação de um novo Pix estaria diagnosticando sobre o design?

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

Programando para Abstrações

Dica

Julgue V ou F — a questão só conta se acertar os 4 itens:

  1. Checkout.finalizar(Pagavel metodo) aceita qualquer classe futura que implemente Pagavel, sem recompilação.
  2. Um erro de compilação ao tentar passar Boleto onde se espera CartaoDeCredito é sempre culpa do compilador, não do design.
  3. Programar para o tipo concreto (CartaoDeCredito) em vez da interface (Pagavel) aumenta o acoplamento do sistema.
  4. Uma interface bem desenhada funciona como um “encaixe universal”, indiferente à identidade concreta de quem a implementa.

Programando para Abstrações — Resposta

Dica

  1. Verdadeiro — é o próprio benefício do Plug-and-Play.
  2. Falso — é um diagnóstico de acoplamento forte no design, não um bug do compilador.
  3. Verdadeiro — exigir a classe concreta é o próprio sintoma de acoplamento forte.
  4. Verdadeiro — é a analogia do “padrão USB-C” desta seção.

Conclusão

O que Aprendemos

  1. Sistemas são redes de colaboradores, não amontoados de classes
  2. Interface (O Quê, estável) vs. Implementação (Como, volátil)
  3. Lei de Demeter: fale só com amigos próximos
  4. Programar para interfaces → sistemas Plug-and-Play

Próxima aula: decompor uma única classe que faz coisas demais — o Princípio da Responsabilidade Única.