設計模式原則之迪米特法則

偶my耶的部落格發表於2015-04-05

迪米特法則的簡寫為 LoD,看清楚中間的那個 o 是小寫。迪米特法則也叫做做最少知識原則(Least Knowledge Principle,簡稱 LKP)說的都是一會事,一個物件應該對其他物件有最少的瞭解,通俗的講一 個類對自己需要耦合或者呼叫的類應該知道的最少,你類內部是怎麼複雜、怎麼的糾纏不清都和我沒關係, 那是你的類內部的事情,我就知道你提供的這麼多 public 方法,我就呼叫這個;迪米特法則包含以下四層 意思:

只和朋友交流。迪米特還有一個英文解釋叫做“Only talk to your immedate friends”,只和直接 的朋友通訊,什麼叫做直接的朋友呢?每個物件都必然會和其他物件有耦合關係,兩個物件之間的耦合就 成為朋友關係,這種關係有很多比如組合、聚合、依賴等等。我們來說個例子說明怎麼做到只和朋友交流。 說是有這麼一個故事,老師想讓體育委員確認一下全班女生來齊沒有,就對他說:“你去把全班女生清 一下。”體育委員沒聽清楚,或者是當時腦子正在回憶什麼東西,就問道:“親哪個?”老師¥#……¥%。 我們來看這個笑話怎麼用程式來實現,先看類圖:

設計模式原則之迪米特法則

Teacher.java 的源程式如下:

package com.cbf4life.common;
import java.util.ArrayList;
import java.util.List;
/**
* I'm glad to share my knowledge with you all.
* 老師類
*/
public class Teacher {
//老師對學生髮布命令, 清一下女生
public void commond(GroupLeader groupLeader){
 List<Girl> listGirls = new ArrayList() ;
 //初始化女生
 for(int i=0;i<20;i++){
 listGirls.add(new Girl());
 }
 //告訴體育委員開始執行清查任務
 groupLeader.countGirls(listGirls);
 }
}

老師就有一個方法,釋出命令給體育委員,去清查一下女生的數量。 下面是體育委員 GroupLeader.java 的源程式:

package com.cbf4life.common;
import java.util.List;
/**
* @author cbf4Life cbf4life@126.com
* I'm glad to share my knowledge with you all.
* 體育委員,這個太難翻譯了都是中國的特色詞彙
*/
public class GroupLeader {
//有清查女生的工作
public void countGirls(List<Girl> listGirls){
 System. out.println(" 女生數量是: "+listGirls.size());
 }
}

下面是 Girl.java,就宣告一個類,沒有任何的程式碼:

package com.cbf4life.common;
/**
* @author cbf4Life cbf4life@126.com
* I'm glad to share my knowledge with you all.
* 女生
*/
public class Girl {
}

我們來看這個業務呼叫類 Client:

package com.cbf4life.common;
/**
* @author cbf4Life cbf4life@126.com
* I'm glad to share my knowledge with you all.
* 我們使用Client來描繪一下這個場景
*/
public class Client {
public static void main(String[] args) {
 Teacher teacher= new Teacher();
 //老師釋出命令
 teacher.commond(new GroupLeader());
 }
}

執行的結果如下:

女生數量是: 20

我們回過頭來看這個程式有什麼問題,首先來看 Teacher 有幾個朋友,就一個 GroupLeader 類,這個 就是朋友類,朋友類是怎麼定義的呢? 出現在成員變數、方法的輸入輸出引數中的類被稱為成員朋友類, 迪米特法則說是一個類只和朋友類交流, 但是 commond 方法中我們與 Girl 類有了交流,宣告瞭一個 List動態陣列,也就是與一個陌生的類 Girl 有了交流,這個不好,那我們再來修改一下,類圖還是不變,先修改一下 GroupLeader 類,看原始碼:

package com.cbf4life.common2;
import java.util.ArrayList;
import java.util.List;
/**
* @author cbf4Life cbf4life@126.com
* I'm glad to share my knowledge with you all.
* 體育委員,這個太難翻譯了都是中國的特色詞彙
*/
public class GroupLeader {
//有清查女生的工作
public void countGirls(){
 List<Girl> listGirls = new ArrayList<Girl>();
 //初始化女生
 for(int i=0;i<20;i++){
 listGirls.add(new Girl());
 }
 System. out.println(" 女生數量是: "+listGirls.size());
 }
}

下面是 Teacher.java 程式:

package com.cbf4life.common2;
/**
* 
* I'm glad to share my knowledge with you all.
* 老師類
*/
public class Teacher {
//老師對學生髮布命令, 清一下女生
public void commond(GroupLeader groupLeader){
 //告訴體育委員開始執行清查任務
 groupLeader.countGirls();
 }
}

程式做了一個簡單的修改,就是把 Teacher 中的對 List初始化(這個是有業務意義的,產生出 全班的所有人員)移動到了 GroupLeader 的 countGrils 方法中,避開了 Teacher 類對陌生類 Girl 的訪問, 減少系統間的耦合。記住了,一個類只和朋友交流,不與陌生類交流, 不要出現 getA().getB().getC().getD() 這種情況(在一種極端的情況下是允許出現這種訪問:每一個點號後面的返回型別都相同),那當然還要和 JDK API 提供的類交流,否則你想脫離編譯器存在呀!

朋友間也是有距離的。人和人之間是有距離的,太遠就不是朋友了,太近就渾身不自在,這和類間關 系也是一樣,即使朋友類也不能無話不說,無所不知。大家在專案中應該都碰到過這樣的需求:呼叫一個 類,然後必須是先執行第一個方法,然後是第二個方法,根據返回結果再來看是否可以呼叫第三個方法, 或者第四個方法等等,我們用類圖表示一下:

設計模式原則之迪米特法則

很簡單的類圖,實現軟體安裝過程的第一步做什麼、第二步做什麼、第三步做什麼這樣一個過程,我 們來看三個類的原始碼,先看 Wizard 的原始碼:

package com.cbf4life.common3;
import java.util.Random;
/**
* @author cbf4Life cbf4life@126.com
* I'm glad to share my knowledge with you all.
* 按照步驟執行的業務邏輯類
*/
public class Wizard {
private Random rand = new Random(System. currentTimeMillis());
public int first(){
 System. out.println(" 執行第一個方法...");
 return rand.nextInt(100);
 }
//第二步
public int second(){
 System. out.println(" 執行第二個方法...");
 return rand.nextInt(100);
 }
//第三個方法
public int third(){
 System. out.println(" 執行第三個方法...");
 return rand.nextInt(100);
 }
}

分別定義了三個步驟方法,每個步驟中都有相關的業務邏輯完成指定的任務,我們使用一個隨機函式 來代替業務執行的返回值。再來看軟體安裝過程 InstallSoftware 原始碼:

package com.cbf4life.common3;
/**
* @author cbf4Life cbf4life@126.com
* I'm glad to share my knowledge with you all.
* 業務組裝類,負責呼叫各個步驟
*/
public class InstallSoftware {
public void installWizard(Wizard wizard){
 int first = wizard.first();
 //根據first返回的結果,看是否需要執行second
 if(first>50){
 int second = wizard.second();
 if(second>50){
 int third = wizard.third();
 if(third >50){
 wizard.first();
 }
 }
}
}
}

其中 installWizard 就是一個嚮導式的安裝步驟,我們看場景是怎麼呼叫的:

package com.cbf4life.common3;
/**
* @author cbf4Life cbf4life@126.com
* I'm glad to share my knowledge with you all.
* 業務場景
*/
public class Client {
public static void main(String[] args) {
 InstallSoftware invoker = new InstallSoftware();
 invoker.installWizard(new Wizard());
 }
}

這個程式很簡單,執行結果和隨機數有關,我就不貼上上來了。我們想想這個程式有什麼問題嗎? Wizard 類把太多的方法暴露給 InstallSoftware 類了,這樣耦合關係就非常緊了,我想修改一個方法的返 回值,本來是 int 的,現在修改為 boolean,你看就需要修改其他的類,這樣的耦合是極度不合適的, 迪米 特法則就要求類“小氣”一點,儘量不要對外公佈太多的 public 方法和非靜態的 public 變數, 儘量內斂, 多使用 private,package-private、protected 等訪問許可權。我們來修改一下類圖:

設計模式原則之迪米特法則

我們再來看一下程式的變更,先看 Wizard 程式:

package com.cbf4life.common4;
import java.util.Random;
/**
* @author cbf4Life cbf4life@126.com
* I'm glad to share my knowledge with you all.
* 按照步驟執行的業務邏輯類
*/
public class Wizard {
private Random rand = new Random(System. currentTimeMillis());
//第一步
private int first(){
 System. out.println(" 執行第一個方法...");
 return rand.nextInt(100);
 }
//第二步
private int second(){
 System. out.println(" 執行第二個方法...");
 return rand.nextInt(100);
 }
//第三個方法
private int third(){
 System. out.println(" 執行第三個方法...");
 return rand.nextInt(100);
 }
//軟體安裝過程
public void installWizard(){
 int first = this.first();
 //根據first返回的結果,看是否需要執行second
 if(first>50){
 int second = this.second();
 if(second>50){
 int third = this.third();
 if(third >50){
 this.first();
 }
 }
 }
 }
}

三個步驟的訪問許可權修改為 private,同時把 installeWizad 移動的 Wizard 方法中,這樣 Wizard 類就 對外只公佈了一個 public 方法,類的高內聚特定顯示出來了。我們再來看 InstallSoftware 原始碼:

package com.cbf4life.common4;
/**
* @author cbf4Life cbf4life@126.com
* I'm glad to share my knowledge with you all.
* 業務組裝類,負責呼叫各個步驟
*/
public class InstallSoftware {
public void installWizard(Wizard wizard){
 //不廢話,直接呼叫
 wizard.installWizard();
 }
}

Client 類沒有任何改變,就不在拷貝了,這樣我們的程式就做到了弱耦合,一個類公佈越多的 public 屬性或方法,修改的涉及面也就越大,也就是變更引起的風險就越大。因此為了保持朋友類間的距離,你 需要做的是:減少 public 方法,多使用 private、package-private(這個就是包型別,在類、方法、變數 前不加訪問許可權,則預設為包型別)protected 等訪問許可權,減少非 static 的 public 屬性,如果成員變數 或方法能加上 final 關鍵字就加上,不要讓外部去改變它。

是自己的就是自己的。在專案中有一些方法,放在本類中也可以,放在其他類中也沒有錯誤,那怎麼 去衡量呢?你可以堅持這樣一個原則: 如果一個方法放在本類中,即不增加類間關係,也對本類不產生負 面影響,就放置在本類中。

謹慎使用 Serializable。實話說,這個問題會很少出現的,即使出現也會馬上發現問題。是怎麼回事呢? 舉個例子來說,如果你使用 R M I 的方式傳遞一個物件 VO( Va lu e Object),這個物件就必須使用 Serializable 介面,也就是把你的這個物件進行序列化,然後進行網路傳輸。突然有一天,客戶端的 VO 物件修改了一個 屬性的訪問許可權,從 private 變更為 public 了,如果伺服器上沒有做出響應的變更的話,就會報序列化失敗。 這個應該屬於專案管理範疇,一個類或介面客戶端變更了,而服務端沒有變更,那像話嗎?!

迪米特法則的核心觀念就是類間解耦,弱耦合,只有弱耦合了以後,類的複用率才可以提高,其要求 的結果就是產生了大量的中轉或跳轉類,類只能和朋友交流,朋友少了你業務跑不起來,朋友多了,你項 目管理就複雜,大家在使用的時候做相互權衡吧。

不知道大家有沒有聽過這樣一個理論:“任何 2 個素不相識的人中間最多隻隔著 6 個人,即只用 6 個人 就可以將他們聯絡在一起”,這個理論的學名叫做“六度分離”,應用到我們專案中就是說我和我要呼叫的 類之間最多有 6 次傳遞,呵呵,這隻能讓大家當個樂子來看,在實際專案中你跳兩次才訪問到一個類估計 你就會想辦法了,這也是合理的,迪米特法則要求我們類間解耦,但是解耦是有限度的,除非是計算機的 最小符號二進位制的 0 和 1,那才是完全解耦,我們在實際的專案中時,需要適度的考慮這個法則,別為了套 用法則而做專案,法則只是一個參考,你跳出了這個法則,也不會有人判你刑,專案也未必會失敗,這就 需要大家使用的是考慮如何度量法則了。

相關文章