Law of Demeter

2 minute read

What is Law of Demeter

一言以蔽之,這個定律的涵義就是「不要跟陌生人講話」,在呼叫成員函數的時候,只使用一個 .,因為使用了兩個 .,代表你跟「陌生人」在溝通!

如以下這個例子,hero 間接的跟 getWeapon溝通。

hero.getEquipment().getWeapon()

Precise Definition

這個定律當然有詳細的定義,定義如下:

對於一個類別(Class) C 的某一個方法 (method) M 而言,M 只能呼叫下面這些方法 之一:

  • C
  • 由 M 所產生的物件
  • 傳入 M 的參數
  • C 的資料成員 (member)

Why ?

以下程式碼節錄自 Robert C. Martin (2008). Clean Code: A Handbook of Agile Software Craftsmanship. Pearson Education 一書 P.98:

final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();

這很明顯違反了 Law of Demeter。

首先,我們必須知道 optionsgetScratchDir(),還要再知道 scratchDirgetAbsolutePath(),這樣造成這四的 class 之間的耦合度大幅上升,要人去強記這麼長一大串細節,無疑會造成 code 非常難維護。

另外一種寫法與之類似,叫做 Train Wrecks,他的結構如下:

Option opts = ctxt.getOptions();
File scratchDir = opts.getScratchDir();
final String outputDir = scratchDir.getAbsolutePath();

因為看起來像一堆散落的火車,所以才有此名。 再仔細看,你會發現它只是把原本的程式碼,展開成三行,實質上與原本的程式碼無異!所以它同樣不符合 Law of Demeter。

Solution

很多人想到的解決方法,是透過代理 (delegation),讓我們回到最初簡單的例子,一個代理的方法如下:

原本的 code:

hero.getEquipment().getWeapon()

增加一個 delegation method 之後:

public void getWeapon() {
  return equitment.getWeapon()
}

hero.getWeapon()

關於 delegation 的說明可以參閱此書: Refactoring: Improving the Design of Existing Code

Better Solution

剛剛透過代理 (delegation) 的寫法,再大多數的情況下是一種解法,不過更好的寫法或許是這樣:

假設原本是想要寫一個換武器的函式:

Weapon new_weapon = Sword();
Weapon old_weapon = hero.getEquipment().getWeapon()
hero.getEquipment().setWeapon(new_weapon)
System.out.print("Replace" + old_weapon + "with" + new_weapon);

改成這樣,比較有物件導向程式設計的感覺:

public void switchWeapon(new_weapon) {
  Weapon old_weapon = equitment.dropWeapon();
  equitment.setWeapon(new_weapon);
  System.out.print("Replace" + old_weapon + "with" + new_weapon);
}

Weapon new_weapon = Sword();
hero.switchWeapon(new_weapon);

最後還是要注意,這些法則並不需要強迫遵守,它只是給你一些想法,重要的是程式碼的可讀性、可維護性,千萬不要本末倒置了。

Reference