05.迪米特原则 (LOD)
LOD全称
LOD, Law of Demeter, 迪米特原则 or LKP, Least Knowledge Principle, 最少知识原则
定义
一个对象应该对其他对象有最少的了解。一个类应该对自己需要耦合或调用的类知道的最少,类的内部如何实现与调用者或者依赖者没有关系,调用者或者依赖者只需知道它需要的方法即可。
只与直接的朋友发生通信。
优点
- 降低类之间的耦合度,提高了模块的相对独立性
- 耦合度降低,从而提高了类的可重用率和系统的扩展性
缺点
- 过度使用迪米特原则,会产生大量的中介类,导致系统的复杂度提高。在釆用迪米特法则时需要反复权衡,确保高内聚和低耦合的同时,保证系统的结构清晰
实现
-
问题由来: 类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大
-
解决方案: 尽量降低类与类之间的耦合
-
需强调
- 从依赖者的角度来说,只依赖应该依赖的对象。
- 从被依赖者的角度说,只暴露应该暴露的方法。
-
需注意
- 在类的划分上,应该创建弱耦合的类。类与类之间的耦合越弱,就越有利于实现可复用的目标。
- 在类的结构设计上,尽量降低类成员的访问权限。
- 在类的设计上,优先考虑将一个类设置成不变类。
- 在对其他类的引用上,将引用其他对象的次数降到最低。
- 不暴露类的属性成员,而应该提供相应的访问器(set 和 get 方法)。
- 谨慎使用序列化(Serializable)功能。
实例
例子:有一个集团公司,下属单位有分公司和直属部门,现在要求打印出所有下属单位的员工ID。先来看一下违反迪米特法则的设计。
// 总公司员工
class Employee {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
// 分公司员工
class SubEmployee {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
// 子公司管理
class SubCompanyManager {
public List<SubEmployee> getAllEmployee() {
List<SubEmployee> list = new ArrayList<>();
for (int i = 1; i < 100; i++) {
SubEmployee subEmployee = new SubEmployee();
// 给分公司人员顺序分配一个ID
subEmployee.setId("分公司" + i);
list.add(subEmployee);
}
return list;
}
}
// 总公司管理
class CompanyManager {
public List<Employee> getAllEmployee() {
List<Employee> list = new ArrayList<>();
for (int i = 1; i < 30; i++) {
Employee employee = new Employee();
// 给总公司人员顺序分配一个ID
employee.setId("总公司" + i);
list.add(employee);
}
return list;
}
public void printAllEmployee(SubCompanyManager subCompanyManager) {
// 分公司员工
List<SubEmployee> subEmployeeList = subCompanyManager.getAllEmployee();
for (SubEmployee subEmployee : subEmployeeList) {
System.out.println(subEmployee.getId());
}
// 总公司员工
List<Employee> employeeList = getAllEmployee();
for (Employee employee : employeeList) {
System.out.println(employee.getId());
}
}
}
public class LODClient {
public static void main(String[] args) {
CompanyManager companyManager = new CompanyManager();
companyManager.printAllEmployee(new SubCompanyManager());
}
}
问题出现在CompanyManager类,根据迪米特法则,只与直接的朋友发生通信。而SubEmployee类并不是CompanyManager类的直接朋友(以局部变量出现的耦合不属于直接朋友),从逻辑上讲总公司只与他的分公司耦合就行了,与分公司的员工并没有任何联系,这样设计显然是增加了不必要的耦合。按照迪米特法则,应该避免类中出现这样非直接朋友关系的耦合。修改后的代码如下
// 总公司员工
class Employee {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
// 分公司员工
class SubEmployee {
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
// 子公司管理
class SubCompanyManager {
public List<SubEmployee> getAllEmployee() {
List<SubEmployee> list = new ArrayList<>();
for (int i = 1; i < 100; i++) {
SubEmployee subEmployee = new SubEmployee();
// 给分公司人员顺序分配一个ID
subEmployee.setId("分公司" + i);
list.add(subEmployee);
}
return list;
}
public void printSubCompany() {
List<SubEmployee> subEmployeeList = this.getAllEmployee();
for (SubEmployee subEmployee : subEmployeeList) {
System.out.println(subEmployee.getId());
}
}
}
// 总公司管理
class CompanyManager {
public List<Employee> getAllEmployee() {
List<Employee> list = new ArrayList<>();
for (int i = 1; i < 30; i++) {
Employee employee = new Employee();
// 给总公司人员顺序分配一个ID
employee.setId("总公司" + i);
list.add(employee);
}
return list;
}
public void printAllEmployee(SubCompanyManager subCompanyManager) {
// 分公司员工
subCompanyManager.printSubCompany();
// 总公司员工
List<Employee> employeeList = getAllEmployee();
for (Employee employee : employeeList) {
System.out.println(employee.getId());
}
}
}
public class LODClient {
public static void main(String[] args) {
CompanyManager companyManager = new CompanyManager();
companyManager.printAllEmployee(new SubCompanyManager());
}
}
修改后,为分公司增加了打印人员ID的方法,总公司直接调用来打印,从而避免了与分公司的员工发生耦合。
总结
- 迪米特法则的初衷是降低类之间的耦合,由于每个类都减少了不必要的依赖,因此的确可以降低耦合关系。
- 过分的使用迪米特原则,会产生大量这样的中介和传递类,导致系统复杂度变大。所以在采用迪米特法则时要反复权衡,既做到结构清晰,又要高内聚低耦合。