LOD (Law of Demeter)

LOD (Law of Demeter)

Introduction The name of the principle comes from the research project itself (referencing Demeter, the Greek goddess of agriculture—the metaphor was “cultivating” software that grows incrementally and adaptably, rather than the coupling principle itself). The Demeter project investigated how to reduce the maintenance cost of object-oriented systems, observing that a significant portion of software changes broke code far from the point where the actual change occurred—a ripple effect caused by classes that had deep knowledge of the internal structure of other classes. This observation was empirically confirmed a few years later: in 1994, Chidamber & Kemerer published the famous CK metrics (A Metrics Suite for Object Oriented Design), in which CBO (Coupling Between Objects)—how coupled a class is to others—became one of the strongest predictors of defects and maintenance effort in subsequent empirical software engineering studies. In other words: the intuition behind the Law of Demeter (less coupling = fewer bugs when changing code) is backed by data from decades of empirical software quality research.

简介 该原则的名称源于研究项目本身(引用了希腊农业女神得墨忒耳——其隐喻是“培育”能够增量且适应性增长的软件,而非耦合原则本身)。Demeter 项目旨在研究如何降低面向对象系统的维护成本,他们观察到,很大一部分软件变更会导致远离变更点的代码崩溃——这种连锁反应是由那些深入了解其他类内部结构的类所引起的。这一观察在几年后得到了实证证实:1994 年,Chidamber 和 Kemerer 发表了著名的 CK 度量标准(面向对象设计的度量套件),其中 CBO(对象间的耦合度)——即一个类与其他类的耦合程度——成为了后续软件工程实证研究中预测缺陷和维护工作量的最强指标之一。换句话说:得墨忒耳定律背后的直觉(更少的耦合 = 修改代码时更少的 Bug)得到了数十年软件质量实证研究数据的支持。


Definition Also called the “Principle of Least Knowledge,” the classic formulation is: A method M of an object O should only call methods of:

  • O itself
  • Parameters received by M
  • Any object that M creates/instantiates internally
  • Direct components of O (its attributes/fields)
  • Global variables accessible to O

定义 该原则也被称为“最少知识原则”(Principle of Least Knowledge),其经典表述为:对象 O 的方法 M 应该只调用以下对象的方法:

  • O 本身
  • M 接收到的参数
  • M 内部创建/实例化的任何对象
  • O 的直接组件(其属性/字段)
  • O 可访问的全局变量

Popular Summary “Use only one dot” — avoid code like: pedido.getCliente().getEndereco().getCidade().getNome() This is known as a “train wreck” — each . is a car coupled to the previous one. If the internal structure of Cliente or Endereco changes, all code that performed this traversal breaks, even if it is in a completely unrelated module.

通俗总结 “只使用一个点”——避免编写类似 pedido.getCliente().getEndereco().getCidade().getNome() 的代码。 这被称为“火车残骸”(train wreck)——每一个 . 都是一个耦合在前一个对象上的车厢。如果 ClienteEndereco 的内部结构发生变化,所有执行过这种遍历的代码都会崩溃,即使它们位于完全不相关的模块中。


Why does this matter in practice? When method M does object.getX().getY().method(), it starts to depend on the internal structure of X and Y, not just the public interface of O. This creates three problems measured empirically by software engineering literature:

  1. Implicit coupling: M is coupled to classes it didn’t even know existed (the intermediaries).
  2. Fragility: an internal refactoring in any link of the chain propagates breaks to distant and non-obvious code.
  3. Testing difficulty: to test M, you need to mock the entire chain of objects (Cliente, Endereco, etc.), not just the Pedido.

为什么这在实践中很重要? 当方法 M 执行 object.getX().getY().method() 时,它开始依赖于 X 和 Y 的内部结构,而不仅仅是 O 的公共接口。这导致了软件工程文献中实证测量的三个问题:

  1. 隐式耦合: M 耦合到了它甚至不知道存在的类(中间类)。
  2. 脆弱性: 链条中任何环节的内部重构都会将崩溃传播到遥远且不明显的代码中。
  3. 测试难度: 为了测试 M,你需要模拟整个对象链(Cliente, Endereco 等),而不仅仅是 Pedido。

Practical Example Imagine two things that exist in your system:

  • A Cliente (the person)
  • A Carteira (which belongs to the client and holds their money)
class Carteira {
  saldo: number;
  remover(valor: number) { this.saldo -= valor; }
}
class Cliente {
  carteira: Carteira;
}

实际示例 想象一下系统中存在的两样东西:

  • 一个 Cliente(人)
  • 一个 Carteira(属于客户并存放其资金的钱包)
class Carteira {
  saldo: number;
  remover(valor: number) { this.saldo -= valor; }
}
class Cliente {
  carteira: Carteira;
}

Way 1 (problematic) In another part of the code, someone wants to charge R$ 50 from the client. They write it like this: cliente.carteira.remover(50); Notice what this line is doing: it jumps inside the Cliente, grabs their Carteira, and manipulates the Carteira directly. Whoever wrote this line needed to know two things that are none of their business:

  • That Cliente has a carteira.
  • That the Carteira has a remover method. This is the problem: outside code is manipulating the client’s wallet as if it were its own.

方式 1(有问题) 在代码的另一部分,有人想向客户收取 50 雷亚尔。他们这样写: cliente.carteira.remover(50); 注意这一行在做什么:它跳进 Cliente 内部,抓取其 Carteira,并直接操作 Carteira。编写这一行的人需要知道两件与他们无关的事情:

  • Cliente 有一个 carteira
  • Carteira 有一个 remover 方法。 这就是问题所在:外部代码正在操纵客户的钱包,就好像它是自己的一样。

Way 2 (correct)

class Cliente {
  carteira: Carteira;
  pagar(valor: number) {
    this.carteira.remover(valor);
  }
}

And whoever wants to charge the client now writes only: cliente.pagar(50); Done. Whoever called this line doesn’t need to know that a wallet exists. They only need to know that “the client knows how to pay.”

方式 2(正确)

class Cliente {
  carteira: Carteira;
  pagar(valor: number) {
    this.carteira.remover(valor);
  }
}

现在,想要向客户收费的人只需写: cliente.pagar(50); 搞定。调用这一行的人不需要知道钱包的存在。他们只需要知道“客户知道如何支付”即可。


Why is Way 2 better? (the real test) Imagine that tomorrow the system changes: instead of Carteira, the client now has a CartaoCredito.

  • In Way 1: every part of the code that had written cliente.carteira.remover(50) breaks, because carteira no longer exists. You need to hunt down these lines scattered throughout the entire project and fix them one by one.
  • In Way 2: only the Cliente class needs to change internally (swapping carteira.remover for cartao.cobrar, for example). Whoever called cliente.pagar(50) continues working without any changes.

为什么方式 2 更好?(真正的考验) 想象一下明天系统发生了变化:客户不再使用 Carteira,而是拥有了 CartaoCredito(信用卡)。

  • 在方式 1 中: 所有写过 cliente.carteira.remover(50) 的代码部分都会崩溃,因为 carteira 不再存在。你需要找出散落在整个项目中的这些行,并逐一修复。
  • 在方式 2 中: 只有 Cliente 类需要在内部进行更改(例如,将 carteira.remover 替换为 cartao.cobrar)。调用 cliente.pagar(50) 的代码无需任何更改即可继续工作。

The rule, in one sentence If to do something you need to write something.partOfInside.anotherPartOfInside, it is a sign that you should have asked the something if it can do it for you itself. In other words: every extra . in the sentence is a bet that that internal structure will never change. The principle exists so you stop betting on that.

一句话总结该规则 如果为了做某事你需要写 something.partOfInside.anotherPartOfInside,这表明你应该问问 something 它自己是否能为你完成这件事。换句话说:句子中多出的每一个 . 都是在赌那个内部结构永远不会改变。该原则的存在就是为了让你停止这种赌博。