Interfaces e o Contrato de Comportamento

Aula 6 — Programação Orientada a Objetos

Autor

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

Data de Publicação

30 de agosto de 2026

Slides Lista de aulas

1 Proposta da Aula

A Aula 5 terminou com uma pergunta em aberto: aplicamos o Princípio da Inversão de Dependência e decidimos que Cliente deveria depender de uma abstração chamada Pagavel, não de Cartao diretamente. Mas o Java é fortemente tipado — como o compilador aceita uma variável Pagavel que ora recebe um Pix, ora um Cartao, sem que essas classes tenham nada em comum? Esta aula responde com o mecanismo de linguagem que torna essa abstração real: a Interface.

O roteiro, em quatro perguntas:

  1. O que exatamente uma interface promete — e o que ela deliberadamente não promete?
  2. Como uma interface, proibida de ter estado, ainda consegue ditar uma regra de negócio?
  3. O que separa uma interface de uma classe abstrata?
  4. Quando o compilador não consegue mais proteger uma regra de negócio, quem assume essa responsabilidade?

2 Interfaces: o Contrato de Comportamento

Uma interface não é uma classe — é um Contrato de Comportamento. Uma classe define o que um objeto é (seu estado); a interface define o que ele faz.

package br.com.loja.model;

public interface Pagavel {
    // Garante que qualquer pagamento saiba verificar o status
    boolean pagamentoConfirmado(String idString);
    // Garante que qualquer pagamento saiba gerar uma cobranca
    String criarCobranca(double valor);
}

Note a ausência de corpos (métodos terminam em ;) e de atributos — a interface é abstração pura, dizendo o quê, nunca como. Em Java, seus métodos são implicitamente public e abstract.

Para que Pix seja do tipo Pagavel, ela precisa implementar todos os métodos do contrato:

public class Pix implements Pagavel {
    @Override
    public boolean pagamentoConfirmado(String idString) {
        // Detalhe tecnico: consulta API do Banco Central.
        // O Pedido nao precisa saber que isso acontece aqui.
        return true;
    }

    @Override
    public String criarCobranca(double valor) {
        return "BR.GOV.BCB.PIX/QRCODE-RANDOM-ID";
    }
}

O @Override sinaliza que estamos cumprindo uma promessa — e se um método do contrato for esquecido, o Java simplesmente não compila. Pense numa tomada elétrica: ela define um contrato (formato dos pinos, voltagem). O sistema elétrico da casa não sabe se o que está plugado é um liquidificador ou uma TV — desde que o aparelho tenha o plugue certo, recebe energia. Um novo aparelho não exige refazer a fiação da casa; ele só precisa se encaixar no contrato já existente.

Diferente da herança de classes (que compartilha “DNA” — estrutura, estado, implementação da classe-mãe), a interface é um “crachá”: qualquer classe, de qualquer origem, pode se tornar Pagavel bastando assinar o contrato. Isso permite que objetos totalmente díspares — uma VendaOnline e uma AssinaturaMensal — sejam tratados de forma idêntica pelo sistema de pagamentos, com uma flexibilidade que a composição simples nunca alcançaria sozinha.

3 Interfaces Modernas: Default, Static, Private

Até o Java 7, interfaces eram 100% abstratas. Desde o Java 8, podem conter comportamento — permitindo evoluir APIs ricas sem quebrar implementações legadas:

public interface Pagavel {
    String criarCobranca(double valor); // abstrato

    // static: utilitario que pertence ao tipo, nao a instancia
    static boolean isValorValido(double v) { return v > 0; }

    // default: comportamento herdavel, nao quebra Pix.java existente
    default void processarComLog(double v) {
        validar(v);
        System.out.println("Processando: " + criarCobranca(v));
    }

    // private (Java 9+): auxiliar interno, organiza o codigo da interface
    private void validar(double v) {
        if (!isValorValido(v)) throw new IllegalArgumentException();
    }
}

O CheckoutController pode usar processarComLog() sem que Pix.java precise ser alterado ou recompilado.

O paradoxo do “Mutador Cego”. Como uma interface, proibida de ter atributos, consegue mudar o estado de um objeto?

public interface Pagavel {
    double getSaldo();                 // a interface "pergunta" o dado
    void setSaldo(double novoSaldo);   // a interface "entrega" o dado

    // a regra reside na abstracao (centralizacao e consistencia)
    default void aplicarJuros(double taxa) {
        if (taxa < 0) throw new IllegalArgumentException();
        double saldoAtual = getSaldo();
        setSaldo(saldoAtual * (1 + taxa)); // muta sem saber onde o dado mora
    }
}

public class Pix implements Pagavel {
    private double saldoReal; // o estado real esta aqui
    @Override public double getSaldo() { return saldoReal; }
    @Override public void setSaldo(double n) { saldoReal = n; }
}

A interface é o “cérebro” (detém a fórmula de juros e a validação); a classe concreta é os “músculos” (armazena e devolve o dado). A interface não sabe se o saldo mora numa variável privada, num banco de dados ou num serviço remoto — só confia que getSaldo()/setSaldo() funcionam. O ganho: todas as implementações (Pix, Boleto, Cartao) seguem exatamente a mesma regra de juros, sem duplicação (DRY).

DicaInterfaces 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.

4 Interface vs. Classe Abstrata

Mesmo com comportamento, a interface não vira uma “classe disfarçada” — a fronteira é o estado:

Característica Interface Moderna Classe Abstrata
Estado Proibido (só public static final) Permitido (atributos de instância)
Herança Uma classe implementa várias Uma classe estende só uma
Propósito Definir um papel/capacidade (o que faz) Definir uma identidade (o que é)

Diretriz de design: se você precisa compartilhar estrutura de dados e proteger estado interno, use uma classe abstrata. Se precisa compartilhar comportamento entre famílias de classes diferentes, e permitir que uma mesma classe assuma vários papéis, a interface é a escolha certa.

Tipos são comportamento, não DNA. Ao declarar Pagavel p, não estamos dizendo o que p é — estamos dizendo o que p pode fazer. Pix pode ter métodos complexos de QR Code; CartaoDeCredito pode ter lógica de criptografia de chip — em termos de dados, podem não ter nada em comum. Mas para o CheckoutController, ambos são do mesmo tipo, porque ambos “sabem pagar”:

List<Pagavel> opcoesReais = clienteLogado.consultaMeiosPagamento();
Pagavel formaEscolhida = opcoesReais.get(escolha - 1); // Pix, Boleto ou Cartao — tanto faz
String id = formaEscolhida.criarCobranca(total);

O código é escrito para a abstração, não para a implementação — se amanhã surgir Cripto implements Pagavel, o controlador não muda uma linha.

DicaInterfaces 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.

5 Exceções como Guardas de Contrato

O compilador garante que você passe um double — mas não sabe que um preço de \(-\$50{,}00\) é um erro conceitual. Quando a assinatura do método (o tipo) não consegue expressar uma regra de negócio, a exceção assume o papel de fiscalizar o contrato.

@Override
public String criarCobranca(double valor) {
    if (valor <= 0) {
        // Falhe o mais rapido possivel
        throw new IllegalArgumentException("Valor invalido: " + valor);
    }
    // So executa se o contrato for respeitado
    return "PIX-ID-" + (long)(Math.random() * 100000000L);
}

O erro de silenciar. O maior pecado em Orientação a Objetos é “ajudar” o erro retornando null, 0 ou false. Isso só empurra o problema para frente — o sistema eventualmente trava com um NullPointerException numa linha que não tem nada a ver com o erro original. A exceção interrompe o fluxo no exato momento da violação, preservando um stack trace útil.

Quem consome o contrato deve estar preparado para a eventual quebra:

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

O try-catch no CheckoutController age como barreira de contenção — o sistema não morre, se recupera. A diretriz de design: objetos de baixo nível (como Pix) lançam exceções; objetos de alto nível (como o Controller) decidem como reagir a elas.

Aplicando o padrão em três novos contratos. A mesma lógica — interface definindo o “o quê”, exceção guardando os limites — resolve três pontos ainda rígidos do sistema:

// Notificacoes: o canal (SMS, e-mail, WhatsApp) deixa de importar
public interface Notificavel {
    void enviar(String destino, String msg) throws NotificationException;
}

// Descontos: um double sozinho aceita valores absurdos; a interface protege a regra
public interface EstrategiaDesconto {
    double aplicar(double valorOriginal);
}
public class DescontoBlackFriday implements EstrategiaDesconto {
    public double aplicar(double valor) {
        if (valor < 0) throw new IllegalArgumentException("Valor base negativo");
        return valor * 0.50;
    }
}

// Logistica: o Pedido para de gerar seu proprio codigo de rastreio
public interface ServicoLogistico {
    String solicitarRastreio(Pedido pedido) throws CarrierUnavailableException;
}

Em cada caso, a exceção não é só um erro — é uma cláusula do contrato: “prometo tentar, mas dependo de algo externo, e aviso formalmente que posso falhar”. O Pedido deixa de saber como notificar, como calcular descontos complexos ou como falar com transportadoras — ele só conhece os contratos, e confia que os especialistas cumprem a promessa ou avisam da falha.

DicaExceçõ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.

6 Conclusão

Voltando às quatro perguntas da abertura:

  1. O que a interface promete: o comportamento (o quê), nunca a implementação (o como) — e deliberadamente nada sobre estado.
  2. Ditar regra sem estado: o padrão Mutador Cego — a interface é o cérebro, a classe concreta são os músculos.
  3. Interface vs. classe abstrata: a fronteira é o estado — proibido numa, permitido na outra.
  4. Quando o compilador não basta: a exceção assume o papel de guardar a regra de negócio que o sistema de tipos não consegue expressar.

Ponte para a Aula 7

Resolvemos o “o quê” (o contrato) e o “quando falha” (a exceção). Falta o “como o compilador aceita múltiplas formas”: a Aula 7 explica o mecanismo de Polimorfismo — em particular o Late Binding — que permite que a mesma variável Pagavel receba, em momentos diferentes, um Pix ou um Cartao, e execute o código certo em cada caso.

Exercícios Soluções