迪米特法则
1393字约5分钟
2022-04-14
定义
迪米特法则(Law Of Demeter, LOD), 又叫作最少知识原则(Least Knowledge Principle, LKP).
重点在于, 只与你的直接朋友交谈, 不跟"陌生人"说话(Talk only to your immediate friends and not to strangers). 如果两个软件实体无须直接通信, 那么就不应当发生直接的相互调用, 可以通过第三方转发该调用. 其目的是降低类之间的耦合度, 提高模块的相对独立性.
迪米特法则中的"朋友"是指: 出现在成员变量、方法的输入输出参数中的类就是直接的朋友.
为什么要用迪米特原则
类与类之间的关系越密切, 耦合度越大, 当一个类发生改变时, 对另一个类的影响也越大.
优点
由于亲合度降低, 从而提高了类的可复用率和系统的扩展性.
缺点
系统中存在大量的中介类, 增加了系统的复杂度.
实现方法
在类的划分上, 应该创建弱耦合的类. 类与类之间的耦合越弱, 就越有利于实现可复用的目标.
在类的结构设计上, 尽量降低类成员的访问权限.
在类的设计上, 优先考虑将一个类设置成不变类.
在对其他类的引用上, 将引用其他对象的次数降到最低.
不暴露类的属性成员, 而应该提供相应的访问器(set 和 get 方法).
谨慎使用序列化(Serializable)功能.
需要使用序列化的场景: 在当前程序之外保存对象并在需要的时候重新获获取对象.
违背迪米特法则的地方: 序列化不必要地暴露了内部的实现并且容易使得一个类对其最初的内部表示产生依赖.
示例
送奶工把"鲜奶"送到客户的家里, 并且从客户手中得到相应的"报酬".
反例
public class Customer {
private String name;//客户姓名
private Wallet Wallet;//客户的钱包
public Customer(String name, test.Wallet wallet) {
this.name = name;
Wallet = wallet;
}
public String getName() {
return name;
}
public Wallet getWallet() {
return Wallet;
}
}
public class Wallet {
private float value;//钱包内的价值
public Wallet(float value) {
this.value = value;
}
/**
* @return 返回当前钱包内的总额
*/
public float getTotalMoney() {
return value;
}
/**
* @param deposit 向钱包中加入的金额数量
*/
public void addMoney(float deposit) {
value += deposit;
}
/**
* @param debit 需要从钱包中扣除的金额数量
*/
public void subtractMoney(float debit) {
value -= debit;
}
}
public class MilkMan {
/**
* 送奶工执行"交易"
*
* @param payment 送奶工应收取的费用
* @param customer 被收取费用的客户对象
*/
public void makeDeal(float payment, Customer customer) {
Wallet wallet = customer.getWallet();
//获取客户的钱包
if (wallet.getTotalMoney() >= payment) {
wallet.subtractMoney(payment);
//从客户的钱包中减去相应的金额
System.out.println("交易产生: 送奶工从钱包中成功拿走" + payment + "元");
}
}
}
public static void main(String[] args) {
//先构造钱包类
Wallet wallet = new Wallet(100);
//在构造客户类
Customer customer = new Customer("小明", wallet);
//构构造送奶工
MilkMan milkMan = new MilkMan();
//模拟送奶工和客户之间的交易
milkMan.makeDeal(5, customer);
}
注意
上面这种写法会导致几个问题:
- 送奶工直接从钱包里面拿钱.
- 客户无法控制拿钱的多少.
- 直接暴露了客户有一个钱包.
- 当钱包的实现发生了变化, 送奶工的函数也需要发生变化.
正例
我们现在重写"客户"和"送奶工"两个类.
public class Customer {
private String name;//客户姓名
private Wallet wallet;//客户的钱包
public Customer2(String name, test.Wallet wallet) {
this.name = name;
wallet = wallet;
}
/**
* 由送奶工获取顾客的钱包变成顾客自己使用钱包支付费用
*
* @param bill 顾客支付的费用
* @return 支付的费用
*/
public float getPayment(float bill) {
if (wallet != null) {
if (wallet.getTotalMoney() > bill) {
wallet.subtractMoney(bill);
}
}
return bill;
}
public String getName() {
return name;
}
public test.Wallet getWallet() {
return wallet;
}
}
public class MilkMan {
/**
* 送奶工执行"交易
*
* @param payment 送奶工应收取的费用
* @param customer 被收取费用的客户对象
*/
public void makeDeal(float payment, Customer2 customer) {
//此处不再获取顾客的钱包, 而是要求顾客自己支付费用
float customerPayment = customer.getPayment(payment);
System.out.println("感谢您的订购!");
}
}
public static void main(String[] args) {
//先构造钱包类
Wallet wallet = new Wallet(100);
//在构造客户类
Customer customer = new Customer("小明", wallet);
//构构造送奶工
MilkMan milkMan = new MilkMan();
//模拟送奶工和客户之间的交易
milkMan.makeDeal(5, customer);
}
经过修改之后可以发现:
- 送奶工没有直接从客户手里拿钱, 而是客户主动发起付款.
- 客户的Wallet类可以随时改变, 但是送奶工的类不需要改变.
- 客户给送奶工仅暴露
getPayment()
函数, 而不暴露付款当中"拿出钱包", "检查钱包里的钱够不够", "取钱"等动作.
总结
方法不能乱调用, 必须调用下面类型的方法才满足迪米特法则:
- 当前对象的其他方法, 即同一个类中的其他方法.
- 传递进来的参数对象的方法.
- 在方法中自己实例化的任何对象的方法.
- 当前对象内部的组件的方法, 即当前对象定义的属性对象的方法.