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

26 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).

DicaO 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).

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.

DicaUma 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).

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.

DicaPor que “lançar uma exceção” é descrito nesta aula como um ato de honestidade do objeto, e “tratá-la” como um ato de resiliência do sistema?

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

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

DicaInterface como Contrato de Comportamento
  1. ( ) Uma interface define o que um objeto faz, não como ele faz ou o que ele guarda internamente.
  2. ( ) Métodos de interface, em Java, terminam com corpo de implementação obrigatório.
  3. ( ) Uma classe torna-se do tipo da interface ao usar a palavra-chave implements e implementar seus métodos.
  4. ( ) @Override sinaliza que a classe está cumprindo uma promessa feita à interface.
DicaInterface como Tipo Puro
  1. ( ) A interface é chamada de “Tipo Puro” porque não impõe hierarquia de herança de estrutura.
  2. ( ) Duas classes de famílias completamente diferentes podem compartilhar o mesmo tipo, desde que implementem a mesma interface.
  3. ( ) A interface, ao contrário da herança, funciona como um “crachá” independente da origem da classe.
  4. ( ) Herança de classe e implementação de interface são exatamente o mesmo mecanismo em Java.
DicaInterfaces Modernas: Default, Static, Private
  1. ( ) Métodos default permitem adicionar comportamento a uma interface sem quebrar implementações legadas.
  2. ( ) Métodos static em interfaces pertencem ao espaço de nomes do tipo, não a uma instância específica.
  3. ( ) Métodos private em interfaces (Java 9+) servem para compartilhar lógica auxiliar entre métodos default.
  4. ( ) A introdução de default methods obriga todas as classes legadas a serem recompiladas e alteradas manualmente.
DicaO Paradoxo do Mutador Cego
  1. ( ) Uma interface pode orquestrar transições de estado, mesmo sem possuir atributos de instância próprios.
  2. ( ) A interface detém a regra de negócio (o “cérebro”); a classe concreta detém o armazenamento (os “músculos”).
  3. ( ) O método default aplicarJuros sabe exatamente onde e como o saldo está fisicamente armazenado.
  4. ( ) Centralizar a regra de juros na interface evita duplicação de código entre Pix, Boleto e Cartao.
DicaInterface vs. Classe Abstrata
  1. ( ) Interfaces modernas são proibidas de manter atributos de instância; classes abstratas podem tê-los.
  2. ( ) Uma classe pode implementar múltiplas interfaces, mas só pode estender uma única classe abstrata.
  3. ( ) Interfaces definem uma identidade (“o que é”); classes abstratas definem um papel (“o que faz”).
  4. ( ) Se a solução exige compartilhar e proteger uma estrutura de dados comum, a classe abstrata é a ferramenta mais adequada.
DicaTipos como Comportamento
  1. ( ) O tipo de um objeto orientado a objetos é definido pelas mensagens às quais ele responde, não pelo que ele guarda.
  2. ( ) Duas classes sem nenhum atributo em comum podem, ainda assim, ser tratadas como do mesmo tipo pelo sistema.
  3. ( ) Declarar uma variável como Pagavel informa ao compilador exatamente qual classe concreta ela contém.
  4. ( ) Programar para o tipo Pagavel, em vez de Pix ou Cartao, é um exemplo de programação para a abstração.
DicaOs Limites do Sistema de Tipos
  1. ( ) O compilador garante que um parâmetro double seja numérico, mas não garante que seu valor faça sentido no domínio.
  2. ( ) Um preço negativo passado para criarCobranca(double valor) é rejeitado automaticamente pelo compilador.
  3. ( ) Regras de negócio que o tipo não consegue expressar precisam ser fiscalizadas em tempo de execução.
  4. ( ) Uma exceção é uma forma de o objeto dizer “recebi o tipo certo, mas os dados violam minhas regras”.
DicaFail-Fast e o Erro de Silenciar
  1. ( ) Retornar null, 0 ou false para sinalizar erro é uma boa prática recomendada nesta aula.
  2. ( ) A filosofia Fail-Fast recomenda interromper o fluxo imediatamente ao detectar uma violação de contrato.
  3. ( ) Falhas silenciosas tendem a se manifestar mais tarde, em pontos distantes da causa raiz do problema.
  4. ( ) Lançar uma exceção no ponto exato da violação facilita o rastreamento via stack trace.
DicaTry-Catch como Barreira de Contenção
  1. ( ) O bloco try-catch no CheckoutController protege a experiência do usuário contra falhas de baixo nível.
  2. ( ) Segundo a diretriz desta aula, componentes de baixo nível devem capturar e silenciar suas próprias exceções.
  3. ( ) Componentes de alto nível são responsáveis por decidir como o sistema reage a uma exceção lançada abaixo.
  4. ( ) Um catch bem posicionado evita que uma falha externa (rede, banco) derrube o fluxo principal do sistema.
DicaNovos Contratos: Notificação, Desconto, Logística
  1. ( ) A interface Notificavel permite trocar o canal de comunicação (e-mail, SMS, WhatsApp) sem alterar Pedido.
  2. ( ) Um double desconto isolado, sem uma interface EstrategiaDesconto, já protege contra valores absurdos como 500%.
  3. ( ) ServicoLogistico abstrai a comunicação com transportadoras, deixando o Pedido livre de gerar seu próprio rastreio.
  4. ( ) Uma exceção declarada com throws numa interface é uma cláusula explícita de que aquele contrato pode falhar.
DicaExceção como Cláusula de Contrato
  1. ( ) Declarar throws NotificationException avisa formalmente que o método pode falhar por razões externas.
  2. ( ) Lançar uma exceção é descrito nesta aula como um ato de honestidade do objeto que a lança.
  3. ( ) Tratar uma exceção corretamente é descrito como um ato de resiliência do sistema como um todo.
  4. ( ) Uma vez capturada, uma exceção deve sempre ser ignorada silenciosamente para não incomodar o usuário.
DicaSíntese: Contratos e Responsabilidade
  1. ( ) Depois da refatoração, Pedido não sabe como enviar e-mails, calcular descontos complexos, ou falar com transportadoras.
  2. ( ) Pedido confia que os especialistas (Notificavel, EstrategiaDesconto, ServicoLogistico) cumprem o contrato ou avisam da falha.
  3. ( ) Programar contra interfaces e proteger regras com exceções são práticas independentes, sem relação entre si.
  4. ( ) Esse design permite trocar implementações concretas em tempo de execução sem alterar o núcleo do sistema.