Chega de “train wreck”: aplique a Lei de Deméter para cortar acoplamento, simplificar testes e proteger seu domínio. Veja quando usar (e quando ignorar).


Conteúdos relacionados a este artigo.
Arquiteto e Engenheiro de Software | Java & IA
Se você já trombou com um código tipo a.getB().getC().fazAlgo() e sentiu aquela sensação de “isso vai dar ruim”, você não tá sendo dramático — na prática, é um detalhe interno do sistema vazando para quem usa.
um módulo não deve enxergar o interior dos objetos que ele manipula.
A Lei de Deméter (o famoso Princípio do Menor Conhecimento) propõe uma regra direta: interaja apenas com seus “amigos imediatos”. Quando você fala com amigo de amigo, aumenta o acoplamento e torna o código mais frágil a mudanças internas.
Quanto mais você depende do trajeto interno A → B → C → D, mais frágil o sistema fica: muda um detalhe no meio e você quebra lugares que nem lembrava que existiam.
Essa ideia vem ficando mais clara pra mim conforme eu avanço na leitura do Clean Code (Robert C. Martin), especialmente no capítulo Objects and Data Structures — seção The Law of Demeter e no trecho Train Wrecks (páginas 97 a 99). É ali que você entende por que esse estilo de “navegação” espalha conhecimento demais e vira dívida técnica com juros.
Conexão rápida: Object Calisthenics
No Object Calisthenics, a 5ª regra é “One Dot Per Line” (um ponto por linha).
Mas a moral não é contar ponto: é não fazer turismo dentro do objeto (Pedido → Cliente → Endereco → Cep).
A Lei de Deméter diz: fale só com seus “amigos imediatos” — um objeto não deveria ficar “navegando” por dentro de outros objetos pra chegar onde quer.
Na prática, ela evita armadilhas como:
A Lei de Deméter não é “proibido usar dois pontos na linha”. Isso é leitura literal e dá ruim.
Deméter é sobre limite de conhecimento: um método não deveria depender da estrutura interna de outro objeto.
Traduzindo: se, para fazer algo, você precisa navegar pela estrutura interna dos objetos (Pedido → Cliente → Endereco → Cep), está aumentando o acoplamento sem perceber.
Pensa em Deméter como uma pergunta de design: “Esse método deveria saber que esse caminho existe?”
Se quiser decidir rápido se algo é “amigo” ou “amigo do amigo”, usa essa tabela:
| Situação | É amigo? | Exemplo | Por quê |
|---|---|---|---|
this | ✅ | this.validate() | você mesmo |
| Campo direto | ✅ | this.cliente | você controla |
| Parâmetro recebido | ✅ | calcular(Pedido p) | veio na assinatura |
| Objeto criado no método | ✅ | new Dinheiro(...) | você criou, você manda |
| Dependência injetada | ✅ | gateway.cotar() | colaborador explícito |
Amigo imediato = this, campos diretos, parâmetro, objeto criado no método, dependência injetada.
Até código “bonitinho” pode esconder dependência do caminho.
Cadeias de chamadas como essa geralmente são consideradas descuidadas e devem ser evitadas.
O problema não é o ponto. É o serviço ficar dependente do caminho.
Funciona hoje. Amanhã alguém muda Endereco, troca o nome do campo ou a estrutura… e você quebra código longe.
Pensa num hotel: para conseguir uma toalha, você fala com a recepção — não com rouparia, corredor ou chaveiro. Se o hotel reorganizar tudo por dentro, seu pedido continua funcionando, porque o ponto de contato não mudou.
No código é a mesma coisa: quando você faz Pedido → Cliente → Endereco → Cep, você não usa o Pedido como contrato público — você depende do layout interno dele.
Se eu mudar Endereco, quantos arquivos eu quebro?
Se a resposta for “um monte” ou “não sei”, já existe acoplamento espalhado.
Você acha que se livrou do problema só porque quebrou ele em 3 variáveis? Sinto muito, mas isso não resolveu nada. Só espalhou o acoplamento em 3 linhas. O trecho que pega o CEP ainda depende do caminho inteiro e o endereço continua sendo “amigo do amigo”.
Sintaticamente bonito. Mas na real? Continua sendo a mesma cadeia de dependência. O serviço de entrega ainda conhece o caminho inteiro até o CEP.
Quando você aplica a Lei de Deméter, é comum pensar:
“vou criar um método no Pedido e pronto, acabou o train wreck”.
Isso não quebra Deméter. Cliente é amigo imediato do Pedido.
O risco é outro: se você repetir esse padrão pra tudo, sua classe vira uma secretária — cheia de atalhos que só repassam chamadas.
Você começa a entupir Pedido com “atalhos de navegação”:
A API cresce, a manutenção piora e você só mudou a chain de lugar.
O mesmo repasse é bem-vindo quando tem intenção de domínio e protege o resto do sistema da estrutura interna dos objetos:
Regra prática (sem filosofar):
Se o método responde uma pergunta que faz sentido no domínio (“CEP de entrega”, “total do pedido”, “pode cancelar?”), ele é intenção.
Se ele existe só pra evitar getA().getB().getC(), ele é secretária.
Quando você vê um train wreck, quase sempre tem mais dois sinais andando colado com ele.
Se um método vive cavando dados de outro objeto pra decidir, ele tá “morando no lugar errado”. Ele não usa o objeto — explora.
Aqui o problema não é o if, é o serviço saber demais sobre a estrutura interna de Cliente/Endereco.
Em vez de perguntar dados e decidir do lado de fora, empurre a decisão pro domínio (método de intenção).
devemos dizê-lo para fazer algo; não devemos perguntá-lo sobre sua estrutura interna.
Regra de review: se você pega dados pra decidir fora, talvez isso deva virar um método de intenção no domínio.
Se isso é uma violação da Lei de Demeter depende se (...) são objetos ou estruturas de dados.
Deméter é mais importante onde existe comportamento e invariantes. Em dados puros, você pode ser mais relaxado, vamos ver alguns casos onde geralmente é aceitável navegar:
Se Endereco for um VO de verdade (imutável, pequeno, estável e sem regra escondida), isso aqui pode ser ok:
A ideia aqui não é “liberar geral”. É só reconhecer que ler dado transparente é diferente de furar invariantes do domínio.
Encadeamentos em APIs estáveis e bem definidas geralmente não são o problema:
Aqui a cadeia não está expondo a estrutura interna do domínio; está só usando API pública consolidada.
Se o encadeamento existe porque a API foi desenhada pra isso, beleza:
Além de builders, isso também vale para query DSLs e pipelines que retornam o próprio tipo. O ponto é: isso não é “dependência do caminho”. É contrato público fluente.
Quando você ver uma chain, pergunta:
Endereco, quantos arquivos quebram?Vamos usar um caso realista: calcular frete. Pra isso, precisamos do CEP de entrega.
A ideia aqui é simples: primeiro você vai ver como o modelo é montado, depois o “train wreck” aparecendo naturalmente, e por fim a refatoração que corta o acoplamento.
Para explicar Deméter sem ruído, usei um recorte de domínio que aparece o tempo todo: pedido → cliente → endereço.
Esse é um caso comum em e-commerce, delivery, ERP e qualquer fluxo de entrega/cobrança.
A cadeia que a gente quer evitar:
Pedido → Cliente → Endereco → CEP
Endereco — o destino (onde mora o dado que a gente quer)O Endereco é o “fim da linha”: ele guarda o CEP.
Quando esse detalhe muda (nome do campo, estrutura do endereço, validação), você não quer quebrar metade do sistema por causa de chamadas encadeadas.
Cliente — o elo do meio (onde o acoplamento começa a crescer)O Cliente existe aqui pra representar o “passo intermediário” que costuma ser exposto via getter (getEndereco()), e é aí que a navegação começa a ficar tentadora.
Pedido — o ponto de entrada (o objeto que todo mundo tem na mão)Em geral, regras de frete/checkout trabalham a partir de um Pedido.
Então quando alguém precisa do CEP, o caminho mais “fácil” vira: entrar no cliente, entrar no endereço… até achar o dado.
No ServicoDeEntrega, você só quer o CEP… mas acaba conhecendo o caminho inteiro:
O ServicoDeEntrega conhece Pedido — ok.
O que não é natural é ele saber que Pedido tem Cliente, que tem Endereco, que tem CEP.
Em vez do serviço navegar por dentro da estrutura interna de pedido, ele pede o que realmente precisa — uma chamada com intenção, não o caminho.
O ponto aqui é: o serviço só conversa com Pedido.
E aí vem a parte importante: Pedido expõe um método de intenção sem virar “depósito de chain”, ele delega para quem faz sentido.
Agora, pra isso não virar só “esconder a chain” dentro do Pedido, a delegação continua do jeito certo:
o Pedido fala com Cliente, e o Cliente fala com Endereco.
Checkpoint: a Lei de Deméter reduz acoplamento porque o chamador para de depender da estrutura interna dos objetos.
Se você já tentou escrever um teste unitário simples e acabou com uma árvore de mocks… isso quase nunca é “porque testar é chato”.
É porque seu código tá turistando por dentro dos objetos.
Se o serviço pega o CEP via pedido.getCliente().getEndereco().getCep(), o teste herda o mesmo caminho.
Resultado: você precisa mockar Pedido → Cliente → Endereco só pra chegar no dado.
Esse teste sofre com: acoplamento ao caminho, refatorações quebrando tudo e foco na estrutura (não na intenção).
Bordão aplicado em testes
Se eu mudar Endereco, quantos testes eu quebro?
Se a resposta for “um monte”, o problema não é o teste: é o acoplamento.
Na versão melhorada com Deméter (ServicoDeEntregaMelhor), o serviço conversa só com Pedido:
Ou seja: o caminho fica escondido atrás de um método de intenção.
E o teste fica mais simples:
Percebe a diferença? Você testa a intenção (“CEP de entrega”), não a estrutura.
Endereco) quebra testes “de regra”, seus testes estão acoplados ao caminho de acesso dos dados, não ao comportamento.Deméter não é sobre “quantos pontos tem na linha”. É sobre não depender da estrutura interna dos objetos.
Quando você precisa fazer Pedido → Cliente → Endereco → Cep, você não tá “usando o domínio”.
Você tá furando o encapsulamento na marra e espalhando acoplamento onde ninguém vê — até o dia que uma refatoração vira efeito dominó.
pedido.getCepEntrega()), não “caminho”.Se seu código depende do trajeto, ele não é robusto — ele é refém da estrutura.
| Getter + chamada no retorno |
| ⚠️ |
pedido.getCliente().comprar() |
| “amigo do amigo” |
| Cadeia 2+ níveis | 🚨 | pedido.getCliente().getEndereco().getCep() | dependência do caminho |
| Fluent API intencional | ✅ | consulta.onde().limitar() | foi desenhado pra encadear |
| VO/DTO simples | ⚠️ | pessoa.getEndereco().getCidade() | pode ser ok se for dado “transparente” |
String cep = pedido.getCliente().getEndereco().getCep();var cliente = pedido.getCliente();
var endereco = cliente.getEndereco();
var cep = endereco.getCep();var cep = Optional.ofNullable(pedido.getCliente())
.map(Cliente::getEndereco)
.map(Endereco::getCep)
.orElse(null);package com.luizdev.articles.lawofdemeter;
public class PedidoRepassador {
private final ClienteMelhor cliente;
public PedidoRepassador(ClienteMelhor cliente) {
this.cliente = cliente;
}
public String getCepEntrega() {
return cliente.getCepEntrega(); // repassa
}
}pedido.getRuaEntrega();
pedido.getCidadeEntrega();
pedido.getBairroEntrega();
pedido.getNumeroEntrega();package com.luizdev.articles.lawofdemeter;
public class ServicoDeEntregaRepassador {
public String getCepEntrega(PedidoRepassador pedido) {
return pedido.getCepEntrega(); // serviço só fala com Pedido
}
}// o serviço tá com inveja do Cliente/Endereco
if (pedido.getCliente().getEndereco().getUf().equals("PE")) {
aplicarFreteEspecial();
}// em vez de perguntar UF pra decidir fora:
if (pedido.getCliente().getEndereco().getUf().equals("PE")) ...
// diga a intenção:
if (pedido.entregaEmPernambuco()) ...var cidade = pessoa.getEndereco().getCidade();String nome = " Ednaldo ".trim().toLowerCase();var nomes = lista.stream()
.map(String::trim)
.map(String::toLowerCase)
.toList();var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.exemplo.com/itens"))
.timeout(Duration.ofSeconds(2))
.header("Accept", "application/json")
.GET()
.build();package com.luizdev.articles.lawofdemeter;
public class Endereco {
private final String cep;
public Endereco(String cep) {
this.cep = cep;
}
public String getCep() {
return cep;
}
}package com.luizdev.articles.lawofdemeter;
import com.luizdev.articles.lawofdemeter.Endereco;
public class Cliente {
private final Endereco endereco;
public Cliente(Endereco endereco) {
this.endereco = endereco;
}
public Endereco getEndereco() {
return endereco;
}
}package com.luizdev.articles.lawofdemeter;
import com.luizdev.articles.lawofdemeter.Cliente;
public class Pedido {
private final Cliente cliente;
public Pedido(Cliente cliente) {
this.cliente = cliente;
}
public Cliente getCliente() {
return cliente;
}
}package com.luizdev.articles.lawofdemeter;
import com.luizdev.articles.lawofdemeter.Pedido;
public class ServicoDeEntrega {
public String getCepEntrega(Pedido pedido) {
return pedido.getCliente().getEndereco().getCep();
}
}package com.luizdev.articles.lawofdemeter;
import com.luizdev.articles.lawofdemeter.Pedido;
public class ServicoDeEntregaMelhor {
public String getCepEntrega(Pedido pedido) {
return pedido.getCepEntrega();
}
}package com.luizdev.articles.lawofdemeter;
public class PedidoMelhor {
private final ClienteMelhor cliente;
public PedidoMelhor(ClienteMelhor cliente) {
this.cliente = cliente;
}
public String getCepEntrega() {
return cliente.getCepEntrega();
}
}package com.luizdev.articles.lawofdemeter;
public class ClienteMelhor {
private final Endereco endereco;
public ClienteMelhor(Endereco endereco) {
this.endereco = endereco;
}
public Endereco getEndereco() {
return endereco;
}
public String getCepEntrega() {
return endereco.getCep();
}
}package com.luizdev.articles.lawofdemeter;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.Test;
public class TesteServicoDeEntrega {
@Test
void deve_obter_cep_para_calcular_frete() {
Pedido pedido = mock(Pedido.class);
Cliente cliente = mock(Cliente.class);
Endereco endereco = mock(Endereco.class);
when(pedido.getCliente()).thenReturn(cliente);
when(cliente.getEndereco()).thenReturn(endereco);
when(endereco.getCep()).thenReturn("50000-000");
ServicoDeEntrega servico = new ServicoDeEntrega();
String cep = servico.getCepEntrega(pedido);
assertEquals("50000-000", cep);
}
}return pedido.getCepEntrega();package com.luizdev.articles.lawofdemeter;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.Test;
public class TesteServicoDeEntregaMelhor {
@Test
void deve_obter_cep_para_calcular_frete() {
Pedido pedido = mock(Pedido.class);
when(pedido.getCepEntrega()).thenReturn("50000-000");
ServicoDeEntregaMelhor servico = new ServicoDeEntregaMelhor();
String cep = servico.getCepEntrega(pedido);
assertEquals("50000-000", cep);
}
}Qual é a ideia central da Lei de Deméter?