Law of Demeter
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。
首先,我們必須知道 options
有 getScratchDir()
,還要再知道 scratchDir
有 getAbsolutePath()
,這樣造成這四的 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);
最後還是要注意,這些法則並不需要強迫遵守,它只是給你一些想法,重要的是程式碼的可讀性、可維護性,千萬不要本末倒置了。