1987年秋天,迪米特法則由美國Northeastern University的Ian Holland提出咐蚯,被UML的創(chuàng)始者之一Booch等人普及童漩。后來春锋,因為經(jīng)典著作The Pragmatic Programmer而廣為人知。
迪米特法則(Law of Demeter期奔,LoD)又稱為最少知識原則(Least KnowledgePrinciple侧馅,LKP)呐萌,是指一個對象類對于其他對象類來說施禾,知道得越少越好搁胆。也就是說弥搞,兩個類之間不要有過多的耦合關(guān)系渠旁,保持最少關(guān)聯(lián)性攀例。
迪米特法則有一句經(jīng)典語錄:只和朋友通信顾腊,不和陌生人說話粤铭。也就是說杂靶,有內(nèi)在關(guān)聯(lián)的類要內(nèi)聚梆惯,沒有直接關(guān)系的類要低耦合。
就像家里的水管裝修垛吗,有洗衣機地漏、衛(wèi)生間地漏怯屉、廚房地漏蔚舀,但它們最終都匯到同一個污水處理系統(tǒng)里锨络。在平常使用時赌躺,我們不會考慮這些水管是怎么關(guān)聯(lián)流向的羡儿,只需要考慮最上層的使用即可。
設(shè)計模式中的門面模式(Facade)和中介模式(Mediator)掠归,都是迪米特法則應(yīng)用的例子
迪米特法則要求限制軟件實體之間通信的寬度和深度缅叠,正確使用迪米特法則將有以下兩個優(yōu)點拂到。
- 降低了類之間的耦合度码泞,提高了模塊的相對獨立性兄旬。
- 由于親合度降低余寥,從而提高了類的可復(fù)用率和系統(tǒng)的擴展性
從迪米特法則的定義和特點可知领铐,它強調(diào)以下兩點:
- 從依賴者的角度來說,只依賴應(yīng)該依賴的對象宋舷。
- 從被依賴者的角度說祝蝠,只暴露應(yīng)該暴露的方法。
廣義的迪米特法則在類的設(shè)計上的體現(xiàn):
- 優(yōu)先考慮將一個類設(shè)置成不變類细溅。
- 盡量降低一個類的訪問權(quán)限儡嘶。
- 謹(jǐn)慎使用Serializable蹦狂。
- 盡量降低成員的訪問權(quán)限朋贬。
這里用模擬學(xué)生锦募、老師、校長之間關(guān)系的例子來說明迪米特法則來舉例:
老師需要負(fù)責(zé)具體某一個學(xué)生的學(xué)習(xí)情況,而校長會關(guān)心老師所在班級的總體成績党远。
違背原則的方案:
學(xué)生類:
public class Student {
private String name;
private int rank;
private double grade;
public Student(String name,int rank,double grade){
this.name = name;
this.rank = rank;
this.grade = grade;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getRank() {
return rank;
}
public void setRank(int rank) {
this.rank = rank;
}
public double getGrade() {
return grade;
}
public void setGrade(double grade) {
this.grade = grade;
}
}
老師類:
import java.util.ArrayList;
import java.util.List;
public class Teacher {
private String name; //老師姓名
private String clazz; //班級
private static List<Student> studentList;
public Teacher(String name,String clazz){
this.name = name;
this.clazz = clazz;
}
static {
studentList = new ArrayList<>();
studentList.add(new Student("小明",20,530));
studentList.add(new Student("小花",10,590));
studentList.add(new Student("小月",5,680));
studentList.add(new Student("小凱",1,700));
}
public static List<Student> getStudentList(){
return studentList;
}
public String getName() {
return name;
}
public String getClazz() {
return clazz;
}
}
校長類:
import java.util.HashMap;
import java.util.Map;
public class Principal {
private Teacher teacher = new Teacher("李國華","2年級1班");
public Map<String,Object> queryClazzInfo(String clazzId){
int stuCount = clazzStuCount();
double totalScore = clazzTotalScore();
double avgScore = clazzAverageScore();
Map<String,Object> info = new HashMap<>();
info.put("班級",teacher.getClazz());
info.put("老師姓名",teacher.getName());
info.put("學(xué)生人數(shù)",stuCount);
info.put("總分",totalScore);
info.put("平均分",avgScore);
return info;
}
private int clazzStuCount() {
return teacher.getStudentList().size();
}
private double clazzAverageScore() {
return clazzTotalScore() / clazzStuCount();
}
private double clazzTotalScore() {
return teacher.getStudentList().stream().mapToDouble(Student::getGrade).sum();
}
}
校長想知道一個班級的總分和平均分沟娱,是應(yīng)該找老師要济似,還是跟每一個學(xué)生要再進(jìn)行統(tǒng)計呢?顯然是應(yīng)該找具體的班主任老師蓖扑。我們在實際開發(fā)時台舱,容易忽略這樣的真實情況
迪米特法則改造方案:
老師類
import java.util.ArrayList;
import java.util.List;
public class Teacher {
private String name; //老師姓名
private String clazz; //班級
private static List<Student> studentList;
public Teacher(String name,String clazz){
this.name = name;
this.clazz = clazz;
}
static {
studentList = new ArrayList<>();
studentList.add(new Student("小明",20,530));
studentList.add(new Student("小花",10,590));
studentList.add(new Student("小月",5,680));
studentList.add(new Student("小凱",1,700));
}
public static List<Student> getStudentList(){
return studentList;
}
public int clazzStuCount() {
return this.getStudentList().size();
}
public double clazzAverageScore() {
return clazzTotalScore() / clazzStuCount();
}
public double clazzTotalScore() {
return this.getStudentList().stream().mapToDouble(Student::getGrade).sum();
}
public String getName() {
return name;
}
public String getClazz() {
return clazz;
}
}
校長類:
import java.util.HashMap;
import java.util.Map;
public class Principal {
private Teacher teacher = new Teacher("李國華","2年級1班");
public Map<String,Object> queryClazzInfo(String clazzId){
int stuCount = teacher.clazzStuCount();
double totalScore = teacher.clazzTotalScore();
double avgScore = teacher.clazzAverageScore();
Map<String,Object> info = new HashMap<>();
info.put("班級",teacher.getClazz());
info.put("老師姓名",teacher.getName());
info.put("學(xué)生人數(shù)",stuCount);
info.put("總分",totalScore);
info.put("平均分",avgScore);
return info;
}
}
校長類直接調(diào)用老師類的接口柜去,并獲取相應(yīng)的信息拆宛。這樣一來,整個功能邏輯就非常清晰了股耽。
在運用迪米特法則時要注意以下 6 點
- 在類的劃分上钳幅,應(yīng)該創(chuàng)建弱耦合的類贡这。類與類之間的耦合越弱,就越有利于實現(xiàn)可復(fù)用的目標(biāo)。
- 在類的結(jié)構(gòu)設(shè)計上丽惭,盡量降低類成員的訪問權(quán)限。
- 在類的設(shè)計上柜砾,優(yōu)先考慮將一個類設(shè)置成不變類换衬。
- 在對其他類的引用上瞳浦,將引用其他對象的次數(shù)降到最低。
- 不暴露類的屬性成員叫潦,而應(yīng)該提供相應(yīng)的訪問器(set 和 get 方法)。
- 謹(jǐn)慎使用序列化(Serializable)功能短蜕。
缺點
迪米特法則是一種面向?qū)ο笙到y(tǒng)設(shè)計風(fēng)格的一種法則傻咖,尤其適合做大型復(fù)雜系統(tǒng)設(shè)計指導(dǎo)原則。但是也會造成系統(tǒng)的不同模塊之間的通信效率降低警检,使系統(tǒng)的不同模塊之間不容易協(xié)調(diào)等缺點解滓。
同時筝家,因為迪米特法則要求類與類之間盡量不直接通信邻辉,如果類之間需要通信就通過第三方轉(zhuǎn)發(fā)的方式值骇,這就直接導(dǎo)致了系統(tǒng)中存在大量的中介類,這些類存在的唯一原因是為了傳遞類與類之間的相互調(diào)用關(guān)系道伟,這就毫無疑問的增加了系統(tǒng)的復(fù)雜度。解決這個問題的方式是:使用依賴倒轉(zhuǎn)原則祝懂,這樣就可以使調(diào)用方和被調(diào)用方之間有了一個抽象層拘鞋,被調(diào)用方在遵循抽象層的前提下就可以自由的變化盆色,此時抽象層成了調(diào)用方的朋友。