Interfaces e o Contrato de Comportamento
Aula 6 — Programação Orientada a Objetos
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:
- O que exatamente uma interface promete — e o que ela deliberadamente não promete?
- Como uma interface, proibida de ter estado, ainda consegue ditar uma regra de negócio?
- O que separa uma interface de uma classe abstrata?
- 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).
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
defaultpode chamar outros métodos abstratos da mesma interface, confiando que a classe concreta os implementa. - □ O método
aplicarJurosarmazena 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
staticde 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.
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
defaultestatictornam 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.
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
nullou0para 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 NotificationExceptionna 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:
- O que a interface promete: o comportamento (o quê), nunca a implementação (o como) — e deliberadamente nada sobre estado.
- Ditar regra sem estado: o padrão Mutador Cego — a interface é o cérebro, a classe concreta são os músculos.
- Interface vs. classe abstrata: a fronteira é o estado — proibido numa, permitido na outra.
- 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
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?
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).
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).