Acoplamento e Contratos

Aula 5 — Programação Orientada a Objetos

Autor

Marcos M. Raimundo — Instituto de Computação, UNICAMP

Data de Publicação

30 de agosto de 2026

Slides Lista de aulas

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:

  1. Como uma associação “estruturalmente correta” pode esconder uma violação de encapsulamento?
  2. Como medir o acoplamento com números, em vez de “sentir” que o código está ruim?
  3. Quem deve ser responsável por uma tarefa, quando várias classes têm acesso aos dados envolvidos?
  4. 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) = 1

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

DicaFeature Envy e CBO — Se o CBO de uma classe é baixo, isso já garante que o acoplamento dela é saudável?

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.

DicaA Escala de Myers — Por que o Acoplamento de Estampa é considerado “médio” — pior que Dados, mas melhor que Comum ou Conteúdo?

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.

DicaInversão de Dependência — O que exatamente “se inverteu” no Princípio da Inversão de Dependência?

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 de Cartao (baixo nível).
  • □ Depois do DIP, é Cliente que precisa se adaptar às mudanças em Cartao e Pix.
  • □ A cadeia de if/else para escolher entre Cartao, Pix e Boleto é um sintoma de violação do Princípio Aberto/Fechado.

7 Conclusão

Voltando às quatro perguntas da abertura:

  1. 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.
  2. Medir com números: CBO conta quantas dependências existem; a Escala de Myers classifica quão tóxica cada uma é.
  3. Quem deve fazer o quê: o Especialista na Informação — a classe que já tem os dados necessários.
  4. 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

  1. 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 Pedido e Carrinho.

  2. Entre a classe Pedido e a classe Carrinho, 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.

  3. 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 Pagavel inverte a dependência entre Cliente e Cartao.

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

NotaFalso Desacoplamento
  • □ O acoplamento físico (separação em arquivos) garante, por si só, o desacoplamento lógico entre classes.
  • □ Numa classe RelatorioFinanceiro que recebe um Carrinho pelo construtor e itera diretamente sobre seus itens internos para somar valores, a mesma falha de Feature Envy desta aula se aplica, mesmo em um contexto de relatório, não de pedido.
  • □ 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.
NotaFeature Envy (Inveja de Recursos)
  • □ No limite em que um método usa um único getter de um colaborador, uma única vez, para uma leitura simples e isolada, isso já caracteriza Feature Envy com a mesma severidade do exemplo de calcularCustoTotal() desta aula.
  • □ O sintoma clássico é o uso repetitivo de getters de um colaborador para processar dados externamente.
  • □ Se, em vez de mover a lógica de soma para o Carrinho (Move Method), apenas renomeássemos o método calcularCustoTotal() para somarItensExternamente(), o Feature Envy estaria resolvido.
  • □ Feature Envy não tem relação nenhuma com o princípio “Tell, Don’t Ask”.
NotaA Métrica CBO
  • □ No limite em que uma classe depende de uma única classe externa, mas invoca dezenas de métodos diferentes dela, o CBO dessa classe continua sendo 1, independentemente do número de chamadas.
  • □ 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.
NotaAcoplamento de Conteúdo e Comum
  • □ Se o atributo itens do Carrinho fosse declarado private em vez de public, o método aplicarDescontoManual de Pedido ainda conseguiria fazer c.itens.clear() diretamente, sem nenhuma mudança adicional de design.
  • □ Atributos public favorecem 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.
NotaAcoplamento de Estampa e de Dados
  • □ Num sistema de RH que passa um objeto Funcionario inteiro para um método que só precisa calcular o desconto do INSS a partir do salário, isso é o mesmo padrão de Acoplamento de Estampa visto no exemplo de NotificacaoEmail/Cliente desta aula.
  • □ Renomear a classe passada por Estampa pode quebrar o receptor, mesmo sem mudança na lógica de negócio.
  • □ No limite em que um serviço Notificacao recebe cada vez mais parâmetros primitivos individuais (não um objeto), manter o Acoplamento de Dados se torna, na prática, cada vez mais difícil de gerenciar, mesmo sem nenhuma perda de reutilização.
  • □ Refatorar de Estampa para Dados torna o serviço menos reutilizável, pois perde contexto.
NotaGRASP: Especialista na Informação
  • □ Se duas classes, Pedido e Cliente, tivessem acesso igual aos dados de endereço de entrega, mas Cliente fosse quem originalmente recebe esse dado do usuário, o GRASP recomendaria colocar a lógica de validação de endereço em Pedido, e não em Cliente.
  • □ Segundo o GRASP, o Pedido deveria calcular o total, já que ele “contém” o Carrinho.
  • □ Num sistema onde o cálculo de imposto de um Produto está espalhado em três classes diferentes que só “pegam” o preço via getter, aplicar o Especialista na Informação implicaria mover essa lógica para dentro da própria classe Produto.
  • □ No limite em que uma única classe do sistema concentra toda a informação de todos os domínios de negócio, o GRASP do Especialista na Informação recomendaria centralizar ainda mais responsabilidades nela, já que ela já detém os dados.
NotaO Limite da Composição
  • Cliente com private Cartao cartao acoplado a uma classe concreta é um exemplo do limite da composição básica.
  • □ Adicionar Pix e Boleto como novos atributos opcionais em Cliente, com if/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.
NotaInversão de Dependência (DIP)
  • □ Se, em vez de uma interface Pagavel, Cliente dependesse de uma classe abstrata FormaDePagamentoAbstrata com métodos concretos parciais, isso ainda seria uma aplicação válida do DIP, desde que Cliente não referenciasse Cartao ou Pix diretamente.
  • □ 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.
  • □ Se, em vez de depender de Pagavel, Cliente continuasse com um único atributo Cartao cartao, mas todo acesso a ele passasse por blocos try/catch genéricos, isso já seria suficiente para dizer que Cliente aplicou o DIP.
NotaO Princípio Aberto/Fechado (OCP) e o DIP
  • □ No limite em que um sistema nunca precisa de nenhuma extensão futura (o conjunto de comportamentos é fixo para sempre), a distinção entre respeitar ou violar o Princípio Aberto/Fechado deixa de ter qualquer consequência prática.
  • □ A cadeia de if/else sobre 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.
NotaBarreira da Tipagem e o Gancho para Polimorfismo
  • □ Em Java, uma variável do tipo Pagavel pode referenciar Pix ou Cartao, 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.
  • □ Num sistema escrito numa linguagem de tipagem dinâmica, como Python sem type hints, a mesma capacidade de aceitar Pix ou Cartao através de uma única variável existiria por duck typing, sem nenhum mecanismo de contrato exigido em tempo de compilação.
  • □ No limite em que existisse só uma única forma de pagamento em todo o sistema (sem Pix, Boleto ou futuros métodos), a necessidade de Polimorfismo para resolver o problema de “múltiplas identidades” desapareceria por completo.
NotaCirurgia de Espingarda e Efeito Cascata
  • □ Se a lógica de soma do carrinho estivesse centralizada num único método de uma única classe (Move Method já aplicado), uma mudança na regra de desconto ainda exigiria caçar e repetir a alteração em quatro classes diferentes.
  • □ No limite em que um sistema tem uma única classe responsável por toda a lógica de negócio, sem nenhuma duplicação de regra em outro lugar, o risco de Cirurgia de Espingarda desaparece por definição, mesmo que o CBO dessa única classe seja extremamente alto.
  • □ Num sistema de e-commerce onde a lógica de cálculo de frete está duplicada em Pedido, NotaFiscal e RelatorioVendas, uma mudança na tabela de frete exigiria caçar e repetir a alteração nos três lugares — o mesmo Efeito Cascata/Cirurgia de Espingarda visto no exemplo de soma do carrinho.
  • □ Reduzir o CBO de uma classe para 1 (uma única dependência externa) já garante, por si só, que a lógica de negócio que ela usa não está duplicada em outras classes do sistema, prevenindo a Cirurgia de Espingarda.
NotaSíntese: do Acoplamento ao Contrato
  • □ 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.