Interfaces e o Contrato de Comportamento

Aula 6 — Programação Orientada a Objetos

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

2026-08-30

Proposta da Aula

A pergunta da Aula 5: como o compilador aceita Pagavel p = new Pix() e depois p = new Cartao()?

A resposta começa aqui: a Interface — o mecanismo de linguagem que torna a abstração Pagavel real.

Roteiro: contrato → interfaces modernas → tipos como comportamento → exceções como guardas.

Interfaces: o Contrato de Comportamento

A Interface Pagavel

public interface Pagavel {
    boolean pagamentoConfirmado(String idString); // sem corpo!
    String criarCobranca(double valor);           // sem estado!
}

Diz o quê, nunca como. Métodos são implicitamente public abstract.

Cumprindo o Contrato: Pix

public class Pix implements Pagavel {
    @Override
    public boolean pagamentoConfirmado(String idString) { return true; }
    @Override
    public String criarCobranca(double valor) { return "PIX-..."; }
}

Esquecer um método do contrato = erro de compilação, não bug em produção.

A Analogia da Tomada Elétrica

A tomada não sabe se é liquidificador ou TV — só exige o plugue certo. Um aparelho novo não exige refazer a fiação da casa.

Interfaces Modernas: Default, Static, Private

Default, Static, Private

static boolean isValorValido(double v) { return v > 0; }      // utilitario do tipo
default void processarComLog(double v) { validar(v); ... }    // herdavel
private void validar(double v) { ... }                        // interno (Java 9+)

Evolução sem ruptura: Pix.java nem precisa saber que processarComLog existe.

O “Mutador Cego”

default void aplicarJuros(double taxa) {
    double saldoAtual = getSaldo();
    setSaldo(saldoAtual * (1 + taxa)); // muta sem saber ONDE o dado mora
}

Interface = cérebro (regra); classe concreta = músculos (armazenamento). Mesma regra, zero duplicação.

Interfaces Modernas e o Mutador Cego — O método default aplicarJuros muda o saldo do objeto. Isso significa que a interface passou a ter estado próprio?

Escreva sua resposta e compare com um colega antes de avançar (2 min). Depois julgue os 4 itens de V/F abaixo — a questão só conta se acertar os 4:

  • □ Um método default pode chamar outros métodos abstratos da mesma interface, confiando que a classe concreta os implementa.
  • □ O método aplicarJuros armazena o novo saldo dentro da própria interface, num atributo privado.
  • □ O padrão Mutador Cego funciona porque a interface conhece o “cérebro” (regra) e delega o armazenamento aos “músculos” (classe concreta).
  • □ Um método static de interface pode ser chamado sem nenhuma instância de uma classe concreta.

Interfaces Modernas e o Mutador Cego — Resposta

Dica

  • ✔ Um método default pode chamar outros métodos abstratos da mesma interface, confiando que a classe concreta os implementa — é assim que aplicarJuros chama getSaldo()/setSaldo().
  • ✗ O método aplicarJuros armazena o novo saldo dentro da própria interface, num atributo privado — a interface continua sem nenhum atributo de instância; o estado mora na classe concreta.
  • ✔ O padrão Mutador Cego funciona porque a interface conhece o “cérebro” (regra) e delega o armazenamento aos “músculos” (classe concreta) — é a definição do Mutador Cego.
  • ✔ Um método static de interface pode ser chamado sem nenhuma instância de uma classe concreta — métodos static pertencem ao tipo, não a uma instância.

Voltando à pergunta: não. A interface continua sem nenhum atributo de instância — aplicarJuros só orquestra a mudança chamando getSaldo()/setSaldo(), que a classe concreta implementa. A interface é o “cérebro” da regra; o estado físico mora inteiramente nos “músculos” da classe concreta.

Interface vs. Classe Abstrata

A Fronteira: o Estado

Interface Classe Abstrata
Estado Proibido Permitido
Herança Múltipla (implements) Única (extends)
Propósito O que faz O que é

Tipo = Comportamento, Não DNA

List<Pagavel> opcoes = clienteLogado.consultaMeiosPagamento();
Pagavel forma = opcoes.get(escolha - 1); // Pix, Boleto, Cartao — tanto faz
String id = forma.criarCobranca(total);  // escrito para a abstracao

Pix e Cartao sem nada em comum em dados — mesmo tipo Pagavel, porque ambos “sabem pagar”.

Interfaces e o Contrato de Tipo — Uma interface pode ter métodos default com lógica de negócio real. Isso a transforma numa “classe disfarçada”?

O que exatamente impede isso? Escreva sua resposta e compare com um colega antes de avançar (2 min). Depois julgue os 4 itens de V/F abaixo — a questão só conta se acertar os 4:

  • □ Uma interface define o que um objeto faz, sem exigir compartilhamento de estrutura interna (“DNA”).
  • □ Métodos default e static tornam a interface equivalente a uma classe comum, incluindo suporte a atributos de instância.
  • □ Duas classes sem nenhum atributo em comum podem, ainda assim, ser do mesmo tipo se implementarem a mesma interface.
  • □ Esquecer de implementar um método do contrato de uma interface é um erro detectado em tempo de compilação.

Interfaces e o Contrato de Tipo — Resposta

Dica

  • ✔ Uma interface define o que um objeto faz, sem exigir compartilhamento de estrutura interna (“DNA”) — é a definição de Tipo Puro desta aula.
  • ✗ Métodos default e static tornam a interface equivalente a uma classe comum, incluindo suporte a atributos de instância — mesmo com default/static, a interface continua proibida de ter atributos de instância.
  • ✔ Duas classes sem nenhum atributo em comum podem, ainda assim, ser do mesmo tipo se implementarem a mesma interface — é exatamente o caso de Pix e CartaoDeCredito.
  • ✔ Esquecer de implementar um método do contrato de uma interface é um erro detectado em tempo de compilação — o compilador impede a compilação se o contrato não for cumprido.

Voltando à pergunta: não. O que impede a interface de virar uma classe disfarçada é a proibição de atributos de instância — mesmo com default/static, ela nunca guarda estado próprio; métodos default só compartilham comportamento entre implementações que continuam livres para ter estruturas de dados completamente diferentes.

Exceções como Guardas de Contrato

Fail-Fast no Contrato

public String criarCobranca(double valor) {
    if (valor <= 0) throw new IllegalArgumentException("Valor invalido: " + valor);
    return "PIX-ID-...";
}

O compilador aceita qualquer double. A exceção protege o que o tipo não consegue expressar.

O Erro de Silenciar

Retornar null/0/false só empurra o erro para uma linha distante — um NullPointerException sem relação óbvia com a causa real.

O Try-Catch como Contenção

try {
    String id = formaEscolhida.criarCobranca(total);
} catch (IllegalArgumentException e) {
    view.exibirErro("Erro no pagamento: " + e.getMessage());
}

Baixo nível lança; alto nível decide como reagir.

Três Novos Contratos, Mesmo Padrão

public interface Notificavel { void enviar(String d, String m) throws NotificationException; }
public interface EstrategiaDesconto { double aplicar(double valorOriginal); }
public interface ServicoLogistico { String solicitarRastreio(Pedido p) throws CarrierUnavailableException; }

Exceção = cláusula do contrato: “prometo tentar, mas posso falhar — e aviso formalmente”.

Exceções como Guardas de Contrato — Por que “lançar uma exceção” é descrito nesta aula como um ato de honestidade, e “tratá-la” como um ato de resiliência?

Escreva sua resposta e compare com um colega antes de avançar (2 min). Depois julgue os 4 itens de V/F abaixo — a questão só conta se acertar os 4:

  • □ O compilador é suficiente para impedir que um preço negativo seja aceito por um método que recebe double.
  • □ Retornar null ou 0 para sinalizar um erro é uma prática que só adia a descoberta do problema real.
  • □ Segundo a diretriz desta aula, componentes de baixo nível devem lançar exceções, e componentes de alto nível devem decidir como reagir.
  • □ Um throws NotificationException na assinatura de um método é uma forma de tornar explícita uma possível falha do contrato.

Exceções como Guardas de Contrato — Resposta

Dica

  • ✗ O compilador é suficiente para impedir que um preço negativo seja aceito por um método que recebe double — falso: o compilador só garante o tipo sintático (double), não a validade semântica do valor.
  • ✔ Retornar null ou 0 para sinalizar um erro é uma prática que só adia a descoberta do problema real — é o “erro de silenciar” descrito nesta aula.
  • ✔ Segundo a diretriz desta aula, componentes de baixo nível devem lançar exceções, e componentes de alto nível devem decidir como reagir — é a diretriz explícita de responsabilidade.
  • ✔ Um throws NotificationException na assinatura de um método é uma forma de tornar explícita uma possível falha do contrato — é exatamente a ideia de “exceção como cláusula do contrato”.

Voltando à pergunta: lançar a exceção é honestidade porque o objeto de baixo nível admite, no momento exato da violação, que não pode cumprir o contrato — em vez de fingir sucesso com null/0/false. Tratá-la é resiliência porque o componente de alto nível decide como o sistema se recupera, contendo o dano sem deixá-lo se propagar para um NullPointerException distante e sem relação aparente com a causa.

Conclusão

O que Aprendemos

  1. Interface = contrato de comportamento, nunca implementação ou estado
  2. default/static/private trazem comportamento sem quebrar retrocompatibilidade
  3. Interface (papel) vs. Classe Abstrata (identidade): a fronteira é o estado
  4. Exceção guarda a regra de negócio que o tipo sozinho não expressa

Próxima aula: Polimorfismo — como o compilador aceita múltiplas formas por trás do mesmo contrato.