爪哇的得墨忒爾法則
1. 概述
德米特法則 (LoD),或最少知識原則,為模塊化軟件開發提供了面向對象的設計原則。它有助於構建彼此依賴性較低且鬆散耦合的組件。
在本教程中,我們將深入研究德米特定律及其在 Java 中的應用。
2. 理解德墨忒耳定律
德米特法則是面向對象編程中的幾個設計準則之一。它建議對象應避免訪問其他對象的內部數據和方法。相反,對象應該只與其直接依賴項交互。
這個概念首先由 Karl J. Lieberherr et
al
在一篇論文中提出。它指出:
對於所有類
C
以及附加到C
的所有方法M
,M
向其發送消息的所有對像都必須是與以下類關聯的類的實例:
M
的參數類(包括C
)C
類的實例變量(由
M
或由M
調用的函數或方法創建的對像以及全局變量中的對像被視為M
的參數。)
簡而言之,該法律規定類C
的方法X
只能調用以下方法:
-
C
類本身 - 由
X
創建的對象 -
A
n 對像作為參數傳遞給X
- 保存在
C
的實例變量中的對象 - 靜態字段
這條法律概括為五點。
3. Java 中德墨忒耳定律的例子
在上一節中,我們將德墨忒爾定律提煉為五個關鍵規則。讓我們用一些示例代碼來說明這些要點。
3.1.第一條規則
第一條規則規定類C
的方法X
應該只調用C
的方法:
class Greetings {
String generalGreeting() {
return "Welcome" + world();
}
String world() {
return "Hello World";
}
}
這裡, generalGreeting()
方法調用同一個類中的world()
方法。這是符合法律的,因為他們屬於同一階級。
3.2.第二條規則
類C
的方法X
應該只調用由X
創建的對象的方法:
String getHelloBrazil() {
HelloCountries helloCountries = new HelloCountries();
return helloCountries.helloBrazil();
}
在上面的代碼中,我們創建了一個HelloCountries
對象並在其上調用helloBrazil()
。這遵循getHelloBrazil()
方法本身創建對象的法則。
3.3.第三條規則
此外,第三條規則規定方法X
應該只調用作為參數傳遞給 X 的對象**X :**
String getHelloIndia(HelloCountries helloCountries) {
return helloCountries.helloIndia();
}
在這裡,我們將HelloCountries
對像作為參數傳遞給getHelloIndia()
。將對像作為參數傳遞使方法與對象非常接近,並且它可以在不違反 Demeter 規則的情況下調用其方法。
3.4.第四條規則
類C
的方法X
應該只調用C
的實例變量中保存的對象的方法:
// ...
HelloCountries helloCountries = new HelloCountries();
String getHelloJapan() {
return helloCountries.helloJapan();
}
// ...
在上面的代碼中,我們在Greetings
類中創建一個實例變量“ helloCountries
”。然後,我們在getHelloJapan()
方法內的實例變量上調用helloJapan()
() 方法。這符合第四條規則。
3.5.第五條規則
最後,類C
的方法X
可以調用C
中創建的靜態字段的方法:
// ...
static HelloCountries helloCountriesStatic = new HelloCountries();
String getHellStaticWorld() {
return helloCountriesStatic.helloStaticWorld();
}
// ...
這裡,該方法對類中創建的靜態對象調用helloStaticWorld()
方法。
4. 違反德墨忒耳定律
讓我們檢查一些違反德米特定律的示例代碼,並看看可能的修復方法。
4.1.設置
我們首先定義一個Employee
類:
class Employee {
private Department department = new Deparment();
public Department getDepartment() {
return department;
}
}
Employee
類包含一個對象引用成員變量並為其提供訪問器方法。
繼續, Department
類使用以下成員變量和方法進行定義:
class Department {
private Manager manager = new Manager();
public Manager getManager() {
return manager;
}
}
此外, Manager
類包含批准費用的方法:
class Manager {
public void approveExpense(Expenses expenses) {
System.out.println("Total amounts approved" + expenses.total())
}
}
最後,讓我們看看Expenses
類:
class Expenses {
private double total;
private double tax;
public Expenses(double total, double tax) {
this.total = total;
this.tax = tax;
}
public double total() {
return total + tax;
}
}
4.2.用法
這些類通過它們的關係表現出緊密的耦合。我們將通過讓Manager
批准Expenses
來證明違反迪米特法則:
Expenses expenses = new Expenses(100, 10);
Employee employee = new Employee();
employee.getDepartment().getManager().approveExpense(expenses);
在上面的代碼中,我們進行了違反 Demeter 定律的鍊式調用。類之間緊密耦合,無法獨立運行。
讓我們通過將Manager
類作為Employee
中的實例變量來解決此違規問題。它將通過Employee
構造函數傳入。然後,我們將在Employee
類中創建submitExpense()
方法,並在其中調用Manager
上的approveExpense()
:
// ...
private Manager manager;
Employee(Manager manager) {
this.manager = manager;
}
void submitExpense(Expenses expenses) {
manager.approveExpense(expenses);
}
// ...
這是新的用法:
Manager mgr = new Manager();
Employee emp = new Employee(mgr);
emp.submitExpense(expenses);
這種修改後的方法通過減少類之間的耦合併促進更加模塊化的設計來遵循德米特定律。
5. 得墨忒耳定律的例外
鍊式調用通常表示違反了德米特法則,但也有例外。例如,如果構建器是在本地實例化的,則構建器模式不會違反德米特定律。其中一條規則規定“類C
的方法X
應該只調用由X
創建的對象的方法”。
此外,Fluent API 中還存在鍊式調用。如果鍊式調用是在本地創建的對像上,那麼 Fluent API 不會違反 Demeter 定律。但是,當鍊式調用針對非本地實例化的對像或返回不同的對象時,則違反了德米特法則。
此外,在處理數據結構時,有時我們可能會違反迪米特定律。典型的數據結構用法,例如實例化、變異和本地訪問它們,並不違反德米特定律。如果我們在從數據結構獲得的對像上調用方法,則可能會違反德米特定律。
六,結論
在本文中,我們學習了德米特定律的應用以及如何在面向對象代碼中遵守它。此外,我們還通過代碼示例深入研究了每條法律。德米特定律通過將對象交互限制為直接依賴來促進鬆散耦合。
與往常一樣,示例的完整源代碼可在 GitHub 上獲取。