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.

7 Exercícios

7.1 Questões discursivas

  1. Explique o conceito de Interface como um “Contrato de Comportamento” e comente por que ela representa a materialização máxima do paradigma da Caixa Preta. O que diferencia a tipagem pura de uma interface da tipagem baseada em herança de classes?

  2. Analise a técnica do “Mutador Cego”. Como uma interface moderna consegue orquestrar a alteração do saldo de uma conta (aplicar juros), se ela é estritamente proibida de possuir atributos de instância? Explique usando a ideia de inversão de conhecimento (cérebro vs. músculos).

  3. Explique por que o sistema de tipos estáticos do Java (garantir que um parâmetro seja double) é insuficiente para proteger a integridade das regras de negócio. Como o lançamento de exceções atua como fiscalizador do contrato quando os limites da tipagem são atingidos?

7.2 Questões de Verdadeiro/Falso

Cada bloco de 4 itens trata do mesmo tema. A questão só é considerada correta se todos os 4 itens forem julgados corretamente (deixar em branco tem penalidade de 20% da nota da questão).

NotaInterface como Contrato de Comportamento
  • □ Numa interface Voador usada tanto por uma classe Passaro quanto por uma classe Drone, o contrato exige apenas o método voar() — nada nele obriga as duas classes a compartilharem qualquer atributo interno.
  • □ Métodos de interface, em Java, terminam com corpo de implementação obrigatório.
  • □ Se uma classe implementasse apenas dois dos três métodos declarados por uma interface, deixando o terceiro sem corpo e sem a própria classe ser declarada abstract, o código ainda compilaria normalmente, desde que @Override estivesse presente nos dois métodos implementados.
  • □ No limite em que a anotação @Override é removida de um método que de fato sobrescreve um método da interface, o comportamento em tempo de execução (qual método é chamado) muda.
NotaInterface como Tipo Puro
  • □ A interface é chamada de “Tipo Puro” porque não impõe hierarquia de herança de estrutura.
  • □ Duas classes de famílias completamente diferentes podem compartilhar o mesmo tipo, desde que implementem a mesma interface.
  • □ Numa aplicação que precisa que tanto uma classe VeiculoEletrico quanto uma classe Gerador (duas famílias completamente diferentes de objetos) sejam tratadas como Recarregavel, implementar uma interface comum resolveria isso sem exigir que ambas compartilhem uma superclasse.
  • □ Herança de classe e implementação de interface são exatamente o mesmo mecanismo em Java.
NotaInterfaces Modernas: Default, Static, Private
  • □ Se, em vez de usar default, a nova funcionalidade processarComLog fosse adicionada como um método abstrato comum na interface Pagavel, todas as classes existentes que já implementam Pagavel (como Pix) continuariam compilando sem nenhuma alteração.
  • □ No limite em que uma interface só contém métodos static (nenhum método abstrato, nenhum default), ainda seria possível instanciar essa interface diretamente com new, já que ela não teria mais nenhum método pendente de implementação.
  • □ Métodos private em interfaces (Java 9+) servem para compartilhar lógica auxiliar entre métodos default.
  • □ A introdução de default methods obriga todas as classes legadas a serem recompiladas e alteradas manualmente.
NotaO Paradoxo do Mutador Cego
  • □ Uma interface pode orquestrar transições de estado, mesmo sem possuir atributos de instância próprios.
  • □ Numa interface ValidadorDeSenha com um método default validar() que aplica regras de comprimento e complexidade chamando getSenhaAtual() (abstrato), a classe concreta que armazena a senha real desempenha o papel dos “músculos”, enquanto a interface continua sendo o “cérebro” da regra.
  • □ O método default aplicarJuros sabe exatamente onde e como o saldo está fisicamente armazenado.
  • □ No limite em que só existe uma única classe implementando Pagavel em todo o sistema, centralizar aplicarJuros como default na interface, em vez de deixá-lo como método comum daquela única classe, ainda traria o mesmo ganho de evitar duplicação de código.
NotaInterface vs. Classe Abstrata
  • □ Se uma interface Java declarasse um atributo public static final int LIMITE = 10, isso contradiria a regra de que interfaces não podem ter estado.
  • □ Numa aplicação de jogos em que uma classe PersonagemVoador precisa herdar atributos físicos comuns de uma classe abstrata Personagem e, ao mesmo tempo, prometer comportamentos de Voador e de Nadador, ela pode declarar extends Personagem implements Voador, Nadador na mesma linha.
  • □ Interfaces definem uma identidade (“o que é”); classes abstratas definem um papel (“o que faz”).
  • □ Num sistema bancário em que ContaCorrente e ContaPoupanca compartilham o atributo protegido saldo e a lógica de validação de saque, mas cada uma tem regras de rendimento completamente diferentes, uma classe abstrata ContaBancaria (não uma interface) é a ferramenta mais adequada para compartilhar esse estado protegido.
NotaTipos como Comportamento
  • □ O tipo de um objeto orientado a objetos é definido pelas mensagens às quais ele responde, não pelo que ele guarda.
  • □ Numa hierarquia de coleções, ArrayList e HashMap não compartilham nenhuma superclasse de dados em comum além de Object, mas ambas poderiam implementar uma interface Limpavel com um método limpar(), tornando-as do mesmo tipo Limpavel para quem só precisa chamar esse método.
  • □ Declarar uma variável como Pagavel informa ao compilador exatamente qual classe concreta ela contém.
  • □ Se o CheckoutController declarasse a variável formaEscolhida como Pix em vez de Pagavel, mesmo que hoje só Pix fosse usado na prática, isso já eliminaria a vantagem de programar para a abstração caso um Cripto implements Pagavel surgisse amanhã.
NotaOs Limites do Sistema de Tipos
  • □ No limite em que o parâmetro fosse declarado como um tipo customizado ValorMonetario (em vez de double primitivo) que só pudesse ser construído com valores positivos, a validação de negócio migraria do corpo do método para o próprio sistema de tipos, em tempo de compilação.
  • □ Um preço negativo passado para criarCobranca(double valor) é rejeitado automaticamente pelo compilador.
  • □ Num sistema de reservas de voos que aceita um parâmetro int quantidadeAssentos, um valor negativo passa a validação de tipo do compilador da mesma forma que um valor positivo — a mesma lacuna do sistema de tipos vista para o valor de pagamento desta aula se repete aqui.
  • □ Uma exceção é uma forma de o objeto dizer “recebi o tipo certo, mas os dados violam minhas regras”.
NotaFail-Fast e o Erro de Silenciar
  • □ Retornar null, 0 ou false para sinalizar erro é uma boa prática recomendada nesta aula.
  • □ Se, em vez de lançar a exceção imediatamente ao detectar valor <= 0, o método criarCobranca apenas registrasse o erro num log interno e continuasse a execução normalmente até o fim, isso ainda seria uma aplicação válida da filosofia Fail-Fast.
  • □ Falhas silenciosas tendem a se manifestar mais tarde, em pontos distantes da causa raiz do problema.
  • □ Lançar uma exceção no ponto exato da violação facilita o rastreamento via stack trace.
NotaTry-Catch como Barreira de Contenção
  • □ O bloco try-catch no CheckoutController protege a experiência do usuário contra falhas de baixo nível.
  • □ Segundo a diretriz desta aula, componentes de baixo nível devem capturar e silenciar suas próprias exceções.
  • □ No limite em que não existe nenhum componente de alto nível entre o objeto que lança a exceção e o usuário final (a exceção se propaga direto até a interface), a aplicação da diretriz desta aula ainda garantiria uma mensagem de erro tratada e amigável para o usuário.
  • □ Um catch bem posicionado evita que uma falha externa (rede, banco) derrube o fluxo principal do sistema.
NotaNovos Contratos: Notificação, Desconto, Logística
  • □ Numa aplicação de streaming de vídeo que hoje só grava logs de erro em arquivo local, trocar para uma interface Logavel implementada por um adaptador de nuvem seguiria a mesma lógica de Notificavel: o consumidor do log não precisa mudar uma linha.
  • □ Um double desconto isolado, sem uma interface EstrategiaDesconto, já protege contra valores absurdos como 500%.
  • □ Se, em vez de uma interface ServicoLogistico, o Pedido chamasse diretamente uma classe concreta TransportadoraCorreios para gerar o código de rastreio, trocar de transportadora no futuro não exigiria nenhuma alteração em Pedido.
  • □ No limite em que um método de interface declara throws Exception (a superclasse mais genérica possível) em vez de uma exceção específica como CarrierUnavailableException, a cláusula do contrato continua igualmente informativa sobre como e por que o método pode falhar.
NotaExceção como Cláusula de Contrato
  • □ Numa API de pagamento internacional que declara throws CurrencyConversionException, isso avisa ao consumidor da API que a conversão de moeda pode falhar por fatores fora do controle do próprio método (ex.: serviço de cotação indisponível), da mesma forma que criarCobranca avisa sobre falhas de validação.
  • □ Se um método lançasse uma exceção apenas para simular uma falha que nunca ocorre de fato na prática, isso ainda seria consistente com a ideia de exceção como “honestidade sobre um risco real”.
  • □ Num sistema de pagamento que, ao receber uma IllegalArgumentException de um valor inválido, registra o erro, notifica o usuário e continua processando os próximos pedidos da fila sem interromper o serviço inteiro, isso demonstra o mesmo ato de resiliência descrito nesta aula para o tratamento de exceções.
  • □ Uma vez capturada, uma exceção deve sempre ser ignorada silenciosamente para não incomodar o usuário.
NotaSíntese: Contratos e Responsabilidade
  • □ No limite em que Pedido dependesse de dez interfaces diferentes em vez de três, o princípio de que Pedido “não sabe como”, só “confia no contrato”, deixaria de valer, porque o número de dependências já seria alto demais para manter esse desacoplamento.
  • □ Se EstrategiaDesconto não declarasse throws nem lançasse nenhuma exceção em caso de valor inválido, apenas retornando 0 (desconto nulo) silenciosamente, Pedido ainda estaria recebendo a mesma garantia de honestidade do contrato que as demais interfaces desta aula oferecem.
  • □ Programar contra interfaces e proteger regras com exceções são práticas independentes, sem relação entre si.
  • □ Esse design permite trocar implementações concretas em tempo de execução sem alterar o núcleo do sistema.