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).
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).
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.
Escreva sua resposta e compare com um colega antes de avançar (2 min).
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).
- ( ) O acoplamento físico (separação em arquivos) garante, por si só, o desacoplamento lógico entre classes.
- ( ) Passar uma dependência via construtor é estruturalmente correto, mas pode esconder violações de encapsulamento.
- ( ) O paradigma da Caixa Preta permite acessar os dados internos de um colaborador associado, desde que via getter.
- ( ) Ter a referência de um objeto não dá o direito de processar seus dados internos por fora dele.
- ( ) É um code smell onde um método parece mais interessado nos dados de outra classe do que nos próprios.
- ( ) O sintoma clássico é o uso repetitivo de getters de um colaborador para processar dados externamente.
- ( ) A técnica de Move Method cura o problema movendo o comportamento para onde os dados residem.
- ( ) Feature Envy não tem relação nenhuma com o princípio “Tell, Don’t Ask”.
- ( ) CBO conta o número de classes externas distintas com as quais uma classe interage.
- ( ) Refatorar de Feature Envy para delegação pura tende a reduzir o CBO da classe orquestradora.
- ( ) Manter o CBO baixo reduz o risco de Efeito Cascata e facilita testes isolados.
- ( ) O objetivo ideal de qualquer design é atingir CBO igual a zero.
- ( ) O Acoplamento de Conteúdo, o pior nível da Escala de Myers, envolve modificar atributos internos de outra classe.
- ( ) Atributos
publicfavorecem diretamente o Acoplamento de Conteúdo. - ( ) O Acoplamento Comum via métodos estáticos facilita a substituição por mocks em testes automatizados.
- ( ) Uma chamada estática oculta no corpo de um método é uma dependência mais difícil de enxergar do que uma no construtor.
- ( ) O Acoplamento de Estampa ocorre quando se passa um objeto inteiro para usar só uma fração dele.
- ( ) Renomear a classe passada por Estampa pode quebrar o receptor, mesmo sem mudança na lógica de negócio.
- ( ) O Acoplamento de Dados é o nível ideal, comunicando-se só com o estritamente necessário.
- ( ) Refatorar de Estampa para Dados torna o serviço menos reutilizável, pois perde contexto.
- ( ) A responsabilidade de uma tarefa deve pertencer à classe que detém os dados necessários para realizá-la.
- ( ) Segundo o GRASP, o
Pedidodeveria calcular o total, já que ele “contém” oCarrinho. - ( ) Seguir o Especialista na Informação ajuda a manter a lógica de negócio centralizada e testável.
- ( ) O framework GRASP foi consolidado por Craig Larman.
- ( )
Clientecomprivate Cartao cartaoacoplado a uma classe concreta é um exemplo do limite da composição básica. - ( ) Adicionar
PixeBoletocomo novos atributos opcionais emCliente, comif/else, respeita o OCP. - ( ) A explosão de complexidade ciclomática por
if/elseé um sintoma de acoplamento a classes concretas. - ( ) O acoplamento a implementações concretas impede a extensão Plug-and-Play do sistema.
- ( ) Módulos de alto nível não devem depender de módulos de baixo nível; ambos devem depender de abstrações.
- ( ) Abstrações devem depender dos detalhes técnicos das implementações concretas.
- ( ) O DIP permite erguer um “Muro de Fronteira” entre o núcleo do negócio e a infraestrutura.
- ( ) Depois do DIP,
CartaoePixprecisam se adaptar ao contrato imposto porCliente.
- ( ) Um design é “aberto para extensão, fechado para modificação” quando novos comportamentos não exigem reabrir classes antigas.
- ( ) A cadeia de
if/elsesobre tipos concretos de pagamento é compatível com o OCP. - ( ) Depender de uma interface como
Pagavel, em vez de classes concretas, favorece o cumprimento do OCP. - ( ) O DIP e o OCP trabalham juntos: abstrações estáveis permitem estender o sistema sem modificar o núcleo.
- ( ) Em Java, uma variável do tipo
Pagavelpode referenciarPixouCartao, desde que ambos implementem o contrato. - ( ) A composição pura, sem nenhum mecanismo de contrato, já é suficiente para essa substituição em tempo de execução.
- ( ) Interfaces e Herança são os mecanismos de linguagem que tornam esse contrato exigível pelo compilador.
- ( ) O problema de múltiplas classes assumirem a mesma “identidade” perante o sistema é resolvido pelo Polimorfismo.
- ( ) Coesão, CBO, Escala de Myers e GRASP são ferramentas complementares para diagnosticar e melhorar o design.
- ( ) Resolver Feature Envy automaticamente resolve também o acoplamento a classes concretas do Bloco de DIP.
- ( ) Um sistema bem desenhado combina baixo CBO, acoplamento de dados, especialistas corretos e abstrações estáveis.
- ( ) A jornada desta aula termina exatamente onde a próxima começa: como impor contratos de verdade no compilador.