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).
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).
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).
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).
- ( ) Uma interface define o que um objeto faz, não como ele faz ou o que ele guarda internamente.
- ( ) Métodos de interface, em Java, terminam com corpo de implementação obrigatório.
- ( ) Uma classe torna-se do tipo da interface ao usar a palavra-chave
implementse implementar seus métodos. - ( )
@Overridesinaliza que a classe está cumprindo uma promessa feita à interface.
- ( ) 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.
- ( ) A interface, ao contrário da herança, funciona como um “crachá” independente da origem da classe.
- ( ) Herança de classe e implementação de interface são exatamente o mesmo mecanismo em Java.
- ( ) Métodos
defaultpermitem adicionar comportamento a uma interface sem quebrar implementações legadas. - ( ) Métodos
staticem interfaces pertencem ao espaço de nomes do tipo, não a uma instância específica. - ( ) Métodos
privateem interfaces (Java 9+) servem para compartilhar lógica auxiliar entre métodosdefault. - ( ) A introdução de
default methodsobriga todas as classes legadas a serem recompiladas e alteradas manualmente.
- ( ) Uma interface pode orquestrar transições de estado, mesmo sem possuir atributos de instância próprios.
- ( ) A interface detém a regra de negócio (o “cérebro”); a classe concreta detém o armazenamento (os “músculos”).
- ( ) O método
default aplicarJurossabe exatamente onde e como o saldo está fisicamente armazenado. - ( ) Centralizar a regra de juros na interface evita duplicação de código entre
Pix,BoletoeCartao.
- ( ) Interfaces modernas são proibidas de manter atributos de instância; classes abstratas podem tê-los.
- ( ) Uma classe pode implementar múltiplas interfaces, mas só pode estender uma única classe abstrata.
- ( ) Interfaces definem uma identidade (“o que é”); classes abstratas definem um papel (“o que faz”).
- ( ) Se a solução exige compartilhar e proteger uma estrutura de dados comum, a classe abstrata é a ferramenta mais adequada.
- ( ) O tipo de um objeto orientado a objetos é definido pelas mensagens às quais ele responde, não pelo que ele guarda.
- ( ) Duas classes sem nenhum atributo em comum podem, ainda assim, ser tratadas como do mesmo tipo pelo sistema.
- ( ) Declarar uma variável como
Pagavelinforma ao compilador exatamente qual classe concreta ela contém. - ( ) Programar para o tipo
Pagavel, em vez dePixouCartao, é um exemplo de programação para a abstração.
- ( ) O compilador garante que um parâmetro
doubleseja numérico, mas não garante que seu valor faça sentido no domínio. - ( ) Um preço negativo passado para
criarCobranca(double valor)é rejeitado automaticamente pelo compilador. - ( ) Regras de negócio que o tipo não consegue expressar precisam ser fiscalizadas em tempo de execução.
- ( ) Uma exceção é uma forma de o objeto dizer “recebi o tipo certo, mas os dados violam minhas regras”.
- ( ) Retornar
null,0oufalsepara sinalizar erro é uma boa prática recomendada nesta aula. - ( ) A filosofia Fail-Fast recomenda interromper o fluxo imediatamente ao detectar uma violação de contrato.
- ( ) 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.
- ( ) O bloco
try-catchnoCheckoutControllerprotege 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.
- ( ) Componentes de alto nível são responsáveis por decidir como o sistema reage a uma exceção lançada abaixo.
- ( ) Um
catchbem posicionado evita que uma falha externa (rede, banco) derrube o fluxo principal do sistema.
- ( ) A interface
Notificavelpermite trocar o canal de comunicação (e-mail, SMS, WhatsApp) sem alterarPedido. - ( ) Um
double descontoisolado, sem uma interfaceEstrategiaDesconto, já protege contra valores absurdos como 500%. - ( )
ServicoLogisticoabstrai a comunicação com transportadoras, deixando oPedidolivre de gerar seu próprio rastreio. - ( ) Uma exceção declarada com
throwsnuma interface é uma cláusula explícita de que aquele contrato pode falhar.
- ( ) Declarar
throws NotificationExceptionavisa formalmente que o método pode falhar por razões externas. - ( ) Lançar uma exceção é descrito nesta aula como um ato de honestidade do objeto que a lança.
- ( ) Tratar uma exceção corretamente é descrito como um ato de resiliência do sistema como um todo.
- ( ) Uma vez capturada, uma exceção deve sempre ser ignorada silenciosamente para não incomodar o usuário.
- ( ) Depois da refatoração,
Pedidonão sabe como enviar e-mails, calcular descontos complexos, ou falar com transportadoras. - ( )
Pedidoconfia que os especialistas (Notificavel,EstrategiaDesconto,ServicoLogistico) cumprem o contrato ou avisam da falha. - ( ) 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.