迪米特法則(Law of Demeter, LoD)
別名:最少知識原則(Least Knowledge Principle)
核心思想:一個對象應盡可能少地與其他對象發生交互,只與直接的朋友(成員變量、方法參數、方法返回值中的對象)通信,避免依賴間接的類。
原理詳解
-
直接朋友的定義:
- 當前對象的成員變量。
- 當前對象方法的參數。
- 當前對象方法的返回值。
- 當前對象方法中創建的對象(不推薦,但允許)。
-
禁止鏈式調用:
避免出現a.getB().getC().doSomething()
的調用形式,這種“火車殘骸式”代碼會增加耦合性。 -
目標:
- 降低耦合:減少模塊間的依賴,提升代碼可維護性。
- 提高封裝性:隱藏內部實現細節,僅暴露必要接口。
應用案例
場景1:訂單系統獲取用戶配送地址
錯誤設計(違反迪米特法則)
public class User {private Order order;public Order getOrder() { return order; }
}public class Order {private Address shippingAddress;public Address getShippingAddress() { return shippingAddress; }
}public class Address {private String city;public String getCity() { return city; }
}// 客戶端代碼:鏈式調用(直接訪問深層對象)
User user = new User();
String city = user.getOrder().getShippingAddress().getCity();
問題:
- 客戶端需要了解
User
、Order
、Address
的內部結構。 - 修改
Order
或Address
的結構會影響客戶端代碼。
正確設計(遵循迪米特法則)
public class User {private Order order;public String getShippingCity() {return order.getShippingCity(); // 委托給 Order 類}
}public class Order {private Address shippingAddress;public String getShippingCity() {return shippingAddress.getCity(); // 委托給 Address 類}
}public class Address {private String city;public String getCity() { return city; }
}// 客戶端代碼:僅與直接朋友交互
User user = new User();
String city = user.getShippingCity();
優勢:
- 客戶端只需與
User
交互,無需了解Order
和Address
的細節。 - 修改
Order
或Address
的結構不會影響客戶端代碼。
場景2:文件系統目錄結構遍歷
錯誤設計(違反迪米特法則)
public class Directory {private List<File> files;private List<Directory> subDirectories;public List<File> getFiles() { return files; }public List<Directory> getSubDirectories() { return subDirectories; }
}// 客戶端代碼:直接遍歷深層結構
public void printAllFiles(Directory root) {for (File file : root.getFiles()) {System.out.println(file.getName());}for (Directory subDir : root.getSubDirectories()) {printAllFiles(subDir); // 遞歸調用暴露內部結構}
}
問題:
- 客戶端需要了解目錄的遞歸結構,耦合度高。
正確設計(遵循迪米特法則)
public class Directory {private List<File> files;private List<Directory> subDirectories;// 封裝遍歷邏輯,客戶端無需了解內部結構public void traverseFiles(Consumer<File> fileConsumer) {files.forEach(fileConsumer);subDirectories.forEach(subDir -> subDir.traverseFiles(fileConsumer));}
}// 客戶端代碼:僅調用高層方法
Directory root = new Directory();
root.traverseFiles(file -> System.out.println(file.getName()));
優勢:
- 客戶端僅依賴
Directory
的traverseFiles
方法,不關心內部實現。 - 目錄結構的遍歷邏輯被封裝,修改不影響客戶端。
迪米特法則的實踐意義
- 減少耦合:模塊間通過接口通信,而非直接操作內部對象。
- 提升可維護性:修改一個類的內部結構時,無需調整其他模塊。
- 增強可測試性:依賴越少,單元測試越容易隔離和模擬。
常見違反場景及修復
違反場景 | 修復方法 |
---|---|
鏈式調用(a.getB().getC() ) | 封裝中間調用,提供高層接口(如 a.getC() ) |
暴露集合內部結構 | 返回不可變集合或迭代器,避免直接操作 |
方法參數傳遞復雜對象 | 拆分參數為基本類型或接口 |
總結
迪米特法則通過限制對象間的交互范圍,推動代碼向高內聚、低耦合的方向演進。其核心是封裝和委托,適用于任何需要降低依賴關系的場景,尤其在大型系統或模塊化架構中價值顯著。