Acoplamento e Contratos
Aula 5 — Programação Orientada a Objetos
1 Proposta da Aula
Na Aula 4, aprendemos a conectar objetos via Associação e a rotear tarefas via Delegação. É tentador achar que, tendo separado o código em classes e passado as dependências pelo construtor, o sistema já está desacoplado. Não está — o acoplamento físico (como as classes se instanciam) é só metade do problema. Uma classe pode receber a referência de um colaborador “corretamente” e, ainda assim, invadir a caixa preta dele por dentro.
O roteiro, em quatro perguntas:
- Como uma associação “estruturalmente correta” pode esconder uma violação de encapsulamento?
- Como medir o acoplamento com números, em vez de “sentir” que o código está ruim?
- Quem deve ser responsável por uma tarefa, quando várias classes têm acesso aos dados envolvidos?
- O que fazer quando até a delegação bem-feita ainda deixa o núcleo do sistema refém de uma classe concreta?
2 O Falso Desacoplamento
public class Pedido {
private Carrinho carrinho;
public Pedido(Carrinho carrinho) {
this.carrinho = carrinho; // Associacao estrutural correta
}
// VIOLACAO LOGICA: o Pedido invade a "caixa preta" do Carrinho
public double calcularCustoTotal() {
double total = 0.0;
// Feature Envy: o Pedido inveja os dados do Carrinho
for (Produto p : carrinho.getItens()) {
total += p.precoFinal();
}
return total;
}
}A associação via construtor é estruturalmente correta. O erro está em calcularCustoTotal(): em vez de perguntar ao Carrinho “qual é o seu total?”, o Pedido extrai a lista inteira e faz a conta por fora. Isso é Feature Envy (Inveja de Recursos) — um método mais interessado nos dados de um colaborador do que nos seus próprios. O sintoma é a obsessão por getters; o diagnóstico definitivo é notar que qualquer mudança na estrutura interna do Carrinho (trocar List por Map, por exemplo) forçaria mudanças em Pedido, mesmo os dois estando em arquivos separados.
A cura é Move Method: a lógica de soma pertence a quem tem os dados.
public class Pedido {
private Carrinho carrinho;
public Pedido(Carrinho carrinho) { this.carrinho = carrinho; }
// REFATORACAO: delegacao pura, o "como" e responsabilidade do Carrinho
public double calcularCustoTotal() {
return this.carrinho.getTotal();
}
}O Pedido não sabe mais se o Carrinho itera um ArrayList, consulta em tempo real, ou mantém um saldo já calculado — voltou a ser uma caixa preta de verdade. Sem essa correção, se o marketing decidir uma promoção “Compre 3, Leve 4”, e a lógica de soma estiver espalhada em Pedido, Fatura, Checkout e RelatorioFinanceiro, a mudança precisa ser caçada e repetida em todos eles — a Cirurgia de Espingarda já vista na Aula 4, agora com uma causa raiz mais específica: violação de Tell, Don’t Ask.
3 Medindo o Acoplamento: a Métrica CBO
“Sentir” que o código está acoplado demais não basta num ambiente profissional. A Suíte CK (Chidamber & Kemerer) define o CBO (Coupling Between Object Classes): conta a quantas classes externas uma classe está ligada, por métodos, atributos ou parâmetros.
// Cenario A: Acoplamento Invasivo
public class Pedido {
private Carrinho carrinho; // Dependencia 1: Carrinho
public double calcularCustoTotal() {
double total = 0.0;
for (Produto p : carrinho.getItens()) { // Dependencia 2: Produto
total += p.precoFinal();
}
return total;
}
}
// CBO(Pedido) = 2
// Cenario B: Delegacao Pura
public class Pedido {
private Carrinho carrinho; // Dependencia 1: Carrinho
public double calcularCustoTotal() {
return this.carrinho.getTotal(); // Pedido so conhece o Carrinho
}
}
// CBO(Pedido) = 1A diferença parece pequena num exemplo isolado, mas em sistemas com centenas de classes, manter o CBO baixo é o que minimiza a necessidade de testes extensivos e o risco de Efeito Cascata. A regra de ouro não é buscar CBO zero (um sistema sem conexão nenhuma não faz nada) — é manter cada conexão o mais fraca possível, o assunto do próximo bloco.
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:
- □ Passar uma dependência via construtor garante, por si só, o desacoplamento lógico entre as classes.
- □ Feature Envy é o sintoma de um método mais interessado nos dados de outra classe do que nos seus próprios.
- □ Refatorar de Inveja de Recursos para Delegação Pura reduz o CBO da classe orquestradora.
- □ Um CBO baixo, isoladamente, já garante que a qualidade de cada dependência restante é saudável.
4 A Escala de Myers: Qualidade da Dependência
O CBO conta dependências; a Escala de Myers classifica sua toxicidade, do pior para o ideal:
1. Acoplamento de Conteúdo (o pecado original) — uma classe altera diretamente os atributos internos de outra:
public class Carrinho { public List itens = new ArrayList<>(); } // ATRIBUTO PUBLICO!
public class Pedido {
public void aplicarDescontoManual(Carrinho c) {
c.itens.clear(); // VIOLACAO: altera a estrutura do Carrinho por fora
}
}É a morte do encapsulamento — o objeto vira um aglomerado de variáveis globais disfarçadas.
2. Acoplamento Comum (global) — dependência de estado global ou de métodos estáticos:
public class Pedido {
public void verificarPagamento() {
// Dependencia estatica: impossivel testar sem disparar o codigo real
Notificacao.enviarEmailConfirmacao(this.cliente, this.rastreio);
}
}O acoplamento fica “enterrado” no corpo do método, invisível na assinatura — destrói a testabilidade e viola o Princípio Aberto/Fechado (mudar de e-mail para WhatsApp exige mexer em Pedido).
3. Acoplamento de Estampa — passar um objeto inteiro quando só uma fração dele é usada:
public class NotificacaoEmail {
public void enviarConfirmacao(Cliente cliente) {
String email = cliente.getEmail(); // so precisa disso, mas "conhece" o Cliente inteiro
System.out.println("Enviando para: " + email);
}
}Renomear Cliente para Usuario quebra NotificacaoEmail, mesmo que a lógica de e-mail não tenha mudado nada.
4. Acoplamento de Dados (o ideal) — só o dado estritamente necessário trafega:
public class Notificacao {
// Desconhece completamente a existencia de "Cliente"
public static void enviarEmailConfirmacao(String emailDestino, String rastreio) {
System.out.println("Enviando e-mail para: " + emailDestino);
}
}
// O orquestrador extrai o dado antes de chamar:
Notificacao.enviarEmailConfirmacao(this.cliente.getEmail(), this.rastreio);Com essa refatoração, Notificacao vira um serviço reutilizável — pode notificar clientes, fornecedores, administradores, sem carregar toda a bagagem de Cliente junto.
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 Acoplamento de Conteúdo, o pior nível, ocorre quando uma classe modifica diretamente atributos de outra.
- □ O Acoplamento Comum via métodos estáticos é fácil de testar, pois não exige instanciar objetos.
- □ O Acoplamento de Estampa cria dependências transitivas: quem recebe o objeto herda tudo que ele conhece.
- □ O Acoplamento de Dados é o nível ideal, pois módulos comunicam-se só com o estritamente necessário.
5 GRASP: o Especialista na Informação
Para além do diagnóstico intuitivo de code smells, o framework GRASP (Craig Larman) formaliza um critério: a responsabilidade de uma tarefa deve pertencer à classe que possui a maior parte das informações necessárias para cumpri-la — o Especialista na Informação.
“Calcular o custo total” exige conhecer a lista de itens, quantidades e preços — informação que mora inteiramente dentro de Carrinho. A teoria de Larman classifica Carrinho como o Especialista; tentar fazer esse cálculo em Pedido força um roubo de informação (Feature Envy). Respeitar o Especialista na Informação garante que o comportamento siga os dados — resolvendo, ao mesmo tempo, o problema técnico (Feature Envy) e o organizacional (lógica de negócio centralizada e testável).
6 O Limite da Composição e o Muro de Fronteira (DIP)
Mesmo com Feature Envy resolvido, ainda existe uma falha estrutural: acoplamento a classes concretas.
public class Cliente {
private Cartao cartao; // acoplamento forte com implementacao CONCRETA
public void cadastrarCartao(double limite) { this.cartao = new Cartao(limite); }
public void realizarPagamento(double valor) { this.cartao.processar(valor); }
}Cliente (módulo de alto nível, regra de negócio) depende fisicamente de Cartao (módulo de baixo nível, detalhe técnico). Se o negócio pedir Pix e Boleto, o resultado é uma “gambiarra”:
public class Cliente {
private Cartao cartao;
private Pix pix;
private Boleto boleto;
public void pagar(double valor) {
// Violacao do OCP: complexidade ciclomatica explode
if (this.cartao != null) this.cartao.processar(valor);
else if (this.pix != null) this.pix.transferir(valor);
else if (this.boleto != null) this.boleto.gerarCodigoBarra(valor);
}
}Cada novo método de pagamento exige reabrir e recompilar Cliente — violando o Princípio Aberto/Fechado (aberto para extensão, fechado para modificação).
A solução é o Princípio da Inversão de Dependência (DIP, Robert C. Martin): módulos de alto nível não devem depender de módulos de baixo nível — ambos devem depender de abstrações.
public class Cliente {
// Dependemos de uma abstracao! O "Muro de Fronteira" esta erguido.
private Pagavel formaDePagamento;
public void pagar(double valor) {
// Polimorfismo: o Cliente confia cegamente no contrato
this.formaDePagamento.pagamentoConfirmado(valor);
}
}A dependência se inverte: Cliente já não olha para baixo pedindo serviços a Cartao — ele impõe um contrato (Pagavel), e são Cartao e Pix que precisam olhar para cima e obedecer. Se surgir o pagamento por criptomoeda amanhã, Cliente permanece intocado.
O gancho para a próxima aula. Isso levanta um problema técnico: o Java é fortemente tipado — como o compilador aceita que a mesma variável formaDePagamento receba ora um Pix, ora um Cartao, se são classes completamente diferentes? A resposta é o Polimorfismo, viabilizado por Interfaces e Herança — os mecanismos de linguagem que a Aula 6 vai construir.
O que dependia de quê antes, e o que passa a depender de quê depois? 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:
- □ No DIP, tanto o módulo de alto nível quanto o de baixo nível devem depender de uma abstração comum.
- □ Antes do DIP,
Cliente(alto nível) dependia diretamente deCartao(baixo nível). - □ Depois do DIP, é
Clienteque precisa se adaptar às mudanças emCartaoePix. - □ A cadeia de
if/elsepara escolher entreCartao,PixeBoletoé um sintoma de violação do Princípio Aberto/Fechado.
7 Conclusão
Voltando às quatro perguntas da abertura:
- Associação correta, comportamento errado: passar a dependência pelo construtor não impede o objeto de “espreitar” o interior do colaborador — isso ainda é Feature Envy.
- Medir com números: CBO conta quantas dependências existem; a Escala de Myers classifica quão tóxica cada uma é.
- Quem deve fazer o quê: o Especialista na Informação — a classe que já tem os dados necessários.
- Além da delegação: quando até a delegação bem-feita ainda acopla o núcleo a uma classe concreta, a saída é inverter a dependência via uma abstração (DIP).
Ponte para a Aula 6
Terminamos com uma pergunta em aberto: como o compilador permite que a mesma variável aceite tipos completamente diferentes, desde que obedeçam ao mesmo contrato? A Aula 6 responde com os mecanismos de linguagem que tornam isso possível — Interfaces e Herança — e com testes automatizados (JUnit) para verificar que esses contratos são cumpridos.
8 Exercícios
8.1 Questões discursivas
Explique por que a separação de código em arquivos diferentes e o uso de construtores não são, por si só, garantia de desacoplamento. Diferencie acoplamento físico de acoplamento lógico, usando o exemplo de
PedidoeCarrinho.Entre a classe
Pedidoe a classeCarrinho, qual deveria calcular o valor total da compra, segundo o princípio GRASP do Especialista na Informação? Justifique com base em quem possui os dados necessários.O que significa dizer que “módulos de alto nível não devem depender de módulos de baixo nível”? Explique como criar uma interface
Pagavelinverte a dependência entreClienteeCartao.
8.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).