設計模式原則(5)迪米特法則

定義

迪米特法則(LoD,Law of Demeter)也稱最少知識法則(LKP,Least Knowledge Principle): 一個類應該要對自己耦合或調用的類知道的最少。也就是說主調類只關注被調類或被耦合類暴露出來的可訪問方法(如public修飾),並且只關注自己使用的方法,其他的不需要知道。這可以通過減少類間不必要的依賴、降低耦合提高內聚來實現。

關鍵點:迪米特法則對類的低耦合提出了明確要求
秦小波老師用擬人的手法對聽起來難以理解的迪米特進行了詳細的解讀。

1、只和直接朋友交流(Only talk to your immediate friends)

何爲直接朋友?兩個類間發生耦合就可以稱之爲朋友關係,如組合、聚合、依賴(相關概念詳細瞭解可查看UML);體現在代碼上就是:出現在成員變量、方法的輸入輸出參數中的類稱之爲朋友類。
示例:
體育老師想讓班長清點到場的女生。假設這裏涉及到三個類:老師、班長、女生。
先來看看可能的一種代碼實現(相關代碼功能見名知意,砍掉相關繼承關係與接口設計等)

class Girl{}
class Monitor{
     public void countGirls(List<Girl> girlsList) {
          System.out.println("girl number:"+girlsList.size());
     }    
}
class Teacher{
     public void command(Monitor monitor) {
          //initial girl list
          List<Girl> girlsList=new ArrayList(10);
          for(int i=0;i<10;i++) {
              girlsList.add(new Girl());
          }
          //count by Monitor
          monitor.countGirls(girlsList);                  
     }
}
public class Client {
     public static void main(String[] args) {
          //driver
          new Teacher().command(new Monitor());           
     }
}

很明顯,這個代碼結構是有問題的!
類圖:(tool:EA)
在這裏插入圖片描述
我們可以看到:
1、Girl是Monitor的朋友類(在countGirls方法傳參中使用到了Girl類)。
2、Monitor是Teacher的朋友類(同上)。但在command方法中卻使用了Teacher的非朋友類Girl。

問題導火索出現在command方法中:command方法與一個陌生類Girl有了交流,破壞了Teacher類的健壯性,違背了迪米特法則。
那這裏該怎麼修改呢?把girlsList放在Teacher的成員變量或利用傳參?還記得前面說到過的嗎:這裏的Teacher其實並不需要關注Girl類長什麼樣子,僅僅是需要通過Monitor得知Girl的數量。Teacher如果什麼都需要知道的話,那要班長幹嘛?也就是說在上述業務需求下,Girl根本不需要出現在Teacher類中。

綜上對代碼做出修改如下:

class Girl {}
class Monitor {
     List<Girl> girlsList;
     // DIP
     public Monitor(List<Girl> girlsList) {
          this.girlsList = girlsList;
     }
     public void countGirls() {
          System.out.println("girl number:" + girlsList.size());
     }
}
class Teacher {
     public void command(Monitor monitor) {
          monitor.countGirls();
     }
}
public class Client {
     public static void main(String[] args) {
          // driver
          // initial girl list
          List<Girl> girlsList = new ArrayList(10);
          for (int i = 0; i < 10; i++) {
              girlsList.add(new Girl());
          }
          // count by Monitor
          new Teacher().command(new Monitor(girlsList));
     }
}

類圖
在這裏插入圖片描述

此時類間關係就改變了,Teacher不再需要關注Girl。在驅動類中對girlsList進行初始化,Monitor中利用依賴注入傳入girlsList,實現了Teacher與Girl的解耦,增強了系統健壯性。

在某些場景下可能會出現通過一個類的方法獲取到另一個類對象的情況,也就是說這種情況下類間關係通過方法建立。如B.getA();有點類似Spark中的Builder類。一般情況下我們建立類間關係不會基於方法,而是基於類的層面上去建立。如利用成員變量、接口依賴注入

2、朋友間也是有距離的

假設還是以上述代碼爲例,班長在上體育課時不僅需要清點人數,還要根據當時的天氣情況、訓練項目通知同學們去相應的場所上課,清點人數時還要知道哪些人缺席了、哪些人請假了。
這樣一來老師需要班長做的就不僅僅是countGirls了
如下:(其他代碼不變)

class Monitor {
     List<Girl> girlsList;
     // DIP
     public Monitor(List<Girl> girlsList) {
          this.girlsList = girlsList;
     }
     // count
     public void countGirls() {
          System.out.println("girl number:" + girlsList.size());
     }
     // tip
     public void tipClass() {
          System.out.println("the location for current PE is stadium NO.1, please");
     }
     // report
     public void reportClassDetailAboutAbsent() {
          System.out.println("all of Absent number:2. there are tony and mark");
     }
     public void reportClassDetailAboutLeave() {
          System.out.println("all of Leave number:1. who named kitty");
     }

}
class Teacher {
     public void command(Monitor monitor) {
          monitor.tipClass();
          monitor.countGirls();
          monitor.reportClassDetailAboutAbsent();
          monitor.reportClassDetailAboutLeave();
     }
}

類圖
在這裏插入圖片描述
在這裏僅寫了最簡單的方法體,並沒有涉及具體的業務邏輯。Teacher調用了Monitor的四個方法,可以說Monitor在Teacher面前幾乎沒有任何祕密存在了,他們之間的關係過於緊密了。假設上課前的流程發生改變,直觀的就涉及了到兩個類代碼的修改,而這裏還只是最簡單的流程體現而已;因爲他們的緊耦合,變更風險的擴散變得難以掌控。兩個刺蝟取暖,靠的太近就容易刺傷對方。

實際上對於課前的很多事情,體育老師並不需要過於關心,只需要在學期的第一節體育課時和大家有個約定並且讓班長監督就ok了。面對許多突發狀況,班長需要自己去解決,並不需要事事請示體育老師。

在此情況下,Teacher只需要發佈上課的信息就足夠了。
變更後代碼如下:

class Monitor {
     List<Girl> girlsList;
     // DIP
     public Monitor(List<Girl> girlsList) {
          this.girlsList = girlsList;
     }
     // count
     private void countGirls() {
          System.out.println("girl number:" + girlsList.size());
     }
     // tip
     private void tipClass() {
          System.out.println("the location for current PE is stadium NO.1, please");
     }
     // report
     private void reportClassDetailAboutAbsent() {
          System.out.println("all of Absent number:2. there are tony and mark");
     }
     private void reportClassDetailAboutLeave() {
          System.out.println("all of Leave number:1. who named kitty");
     }
     public void PE() {
          tipClass();
          countGirls();
          reportClassDetailAboutAbsent();
          reportClassDetailAboutLeave();
     }
}
class Teacher {
     public void command(Monitor monitor) {
          monitor.PE();
     }
}

類圖:(注意查看訪問權限)
在這裏插入圖片描述
可以看到此時Monitor對於具體的工作方法變爲了private,新增一個公共方法PE對外提供服務。此時若是上課流程發生改變,找Monitor就ok了。我們發現在之前的代碼中提供了太多的public方法,這種對外提供服務的方法越多,變更擴散的風險也就越大。在實際的開發中,能夠縮小方法的訪問範圍就儘量的縮小;能用private就不要用protected!

3、是自己的就是自己的

怎麼理解這句話呢?在實際開發中,可能會遇到具體細節職責不明確的問題,就比如說上述的countGirls,班長可以做老師也可以做呀(在此假設Teacher已經引入Girl的依賴關係)!那這個方法放在哪裏比較合適呢?秦老師這裏提到一條原則:如果一個方法放在本類中,不增加類間關係也不產生負面影響,那就放在本類中

4、謹慎使用Serializable

一開始看到這個標題以爲是關於遠程調用(RPC, Remote Procedure Call)中的全序列化問題,這裏常常涉及到系統優化開發問題。另一個是大數據分佈式開發中必須要記住的一點:你要知道你的代碼運行在哪個角色裏,是否要跨網絡傳輸,哪些對象是不可序列化的(比如說數據庫的connection)。不過這裏好像並不是這麼一回事,更多的是涉及到項目變更管理的問題。

最佳實踐

從依賴關係的引入控制到類間解耦,我們慢慢向系統級開發的高內聚、低耦合目標邁進。不知道有沒有想過這樣的一個問題:解耦要解到什麼樣的一個程度呢?事務都還有個ACID標準呢!那解耦的粒度有一個通用標準嗎?很遺憾也很慶幸,至少以目前軟件工程的發展水平來看:解耦有可遵循的原則,但並沒有必須執行的標準。在代碼層面,我們可以引入第三方類降低類間耦合,但也因此增加了第三方類,類的增加往往意味着系統複雜度的增加,牽一髮而動全身。我們需要在各種開發因素裏面進行一個權衡(trade off),以找到一個至少在當前適用環境下的最優解決方案

參考文獻
秦小波《設計模式之禪 》第二版

發佈了132 篇原創文章 · 獲贊 40 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章