设计模式之——“迪米特法则”(“最少知道”原则)

                 设计模式之——“迪米特法则”(“最少知道”原则)

 

什么是“迪米特法则”


迪米特法则(Law of Demeter)又叫作最少知识原则(Least Knowledge Principle 简写LKP),就是说一个对象应当对其他对象有尽可能少的了解,不和陌生人说话。

上面是摘自百度百科对于“迪米特法则”的解释,相信有很多同学看完这段解释还是处于一脸懵逼的状态,接下来,让我们以一个较为贴近生活的例子里讲解一下“迪米特法则”到底指的是什么东西?又为什么要遵循“迪米特法则”呢?

 

“送奶工”和“客户”的故事


相信有很多同学都曾经订购过“鲜奶”吧,可以包月或者包年付费,也可以每天早上等送奶工直接把鲜奶送到家里来,然后在付钱给送奶工。

我们再来梳理一下整个业务流程:送奶工把“鲜奶”送到客户的家里,并且从客户手中得到相应的“报酬”。让我们来用代码来模拟一下这个业务场景吧!

 

模拟场景


让我们先来模拟一下“客户”对象,“客户”对象会有相应的“”姓名“”属性,或许还会有一个“账户”号,一个“地址”属性,和一些支付方法等等。为了简化描述,我们只给“客户对象”增加“姓名”和“钱包”属性,就像下面这样:

package test;
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;}
}

接下来,眼尖的同学应该发现我们现在需要实现Wallet钱包类了:

package test;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;}}

现在,“客户”和“钱包”类已经设计好了,“送奶工”可以“闪亮登场”了。回忆一下,“送奶工”把“鲜奶”带到你的家门前,按响门铃,你接过鲜奶,支付相应的费用......。我们抽象出“送奶工”的代码:

package test;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 + "元");}}
}

接下来,再写我们最后的驱动类:

package test;public class Driver {public static void main(String[] args) {//先构造钱包类Wallet wallet = new Wallet(100);//在构造客户类Customer customer = new Customer("小明", wallet);//构构造送奶工MilkMan milkMan = new MilkMan();//模拟送奶工和客户之间的交易milkMan.makeDeal(5, customer);}
}

让我们来运行一下这个程序吧,看看结果会是怎样:

“送奶工”从“客户”手中获取到“钱包”,并从钱包中拿走相应的费用.......一切都看似的那么美好!等等,好像哪里出了点问题?

 

哪里出问题了?


这段代码看似“很好”的完成了任务,但是实际上却是一段非常糟糕的代码。为什么这样说呢?让我们再来梳理一遍整个的业务流程。

显然,当送奶工把“鲜奶”送到“客户”家门口时,“送奶工”直接从“客户”身上拿走他的钱包,并从中拿走5元。

我不知道你们是怎么想的,但是我几乎不会让别人直接动我的钱包。更不要说是不认识的陌生人了,万一他拿走你的钱包,取走的不是5元而是500元呢?亦或者直接把你的信用卡给拿走了,这将会是一件很可怕的事情!我们不禁得好好思考一下,送奶工需要直接拿到客户的钱包才能完成交易吗?

这是一个非常关键的地方。送奶工类现在“知道”了客户有一个钱包,并且能够直接随心所欲地操控这个钱包。当我们编译“MilkMan.java”时,他需要依赖一个“Wallet.java”和“Customer.java”,正如下面这段代码片段所示。现在这三个类已经紧紧的“耦合”在一起了。

public void makeDeal(float payment, Customer customer) {Wallet wallet = customer.getWallet();//获取客户的钱包if (wallet.getTotalMoney() >= payment) {

我们还要思考一个在现实生活中发生很普遍的事情:如果哪天客户的钱包被偷了怎么办?此时在代码中相对应的Wallet就被设置成为null了。此时,当送奶工又来到客户家门口时,他还以为能够直接从客户身上拿到钱包,然而等待他的将会是一个“空指针异常”。

当然,有同学会讲了,当送奶工执行钱包的任意方法之前,先检查一下钱包是不是为null在执行相应的方法不就可以了吗?当然可以,但是这样会增加送奶工业务代码的复杂度。

 

“迪米特法则”的威力


接下来,我们需要修改上面的代码,使其变得更接近于我们的真实世界:当送奶工来到我们家门前,他将会要求顾客支付鲜奶的费用。他再也不会像上面的代码描述的那样,直接从顾客手中的钱包拿钱了。或许,他甚至可以不知道顾客是用钱包支付还是用“微信支付”。

新的顾客(Customer2)类:

package test;public class Customer2 {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;}
}

注意上面的代码,顾客再也没有【getWallet()】这个方法了,取而代之的是一个【getPayment()】方法。让我们再来看一下新的送奶工类(MilkMan2)。

package test;public class MilkMan2 {/*** 送奶工执行“交易** @param payment  送奶工应收取的费用* @param customer 被收取费用的客户对象*/public void makeDeal(float payment, Customer2 customer) {//此处不再获取顾客的钱包,而是要求顾客自己支付费用float customerPayment = customer.getPayment(payment);System.out.println("感谢您的订购!");}
}

接下来,让我们来模拟一下送奶工新的一天吧:

package test;public class Driver {public static void main(String[] args) {//先构造钱包类Wallet wallet = new Wallet(100);//在构造客户类Customer2 customer = new Customer2("小明", wallet);//构构造送奶工MilkMan2 milkMan2 = new MilkMan2();//模拟送奶工和客户之间的交易milkMan2.makeDeal(5, customer);}
}

运行后的结果为:

 

为什么变好了?


为什么后面的代码要比我们前面看到的代码好呢?一些同学可能还是喜欢第一次我们写的代码,认为我们只不过仅仅把Customer顾客类对象变得更加的复杂了而已。

首先第一点,第二段的代码明显更加贴近我们的现实生活,现在送奶工是要求顾客主动支付费用,而再也不会直接从顾客身上拿钱包付钱了。

第二点,顾客的Wallet类可以随时改变,但送奶工类却不用跟着变。无论顾客类是改用微信支付,还是支付宝支付,亦或者是零钱支付,只要顾客类对外暴露的接口getPayment()方法声明不变,送奶工根本不在意顾客是用什么方式支付费用的,它只要调用顾客提供的getPayment()方法就好了,具体getPayment()方法是怎样实现的,送奶工并不用关心。这样,代码将会变得更加容易维护,因为顾客类一个小小的改变并不会波及整个源代码,也就是说,其他的代码不用跟着顾客类的变化而一起变化。

“迪米特”大法好


现在我们看到了“迪米特法则”带来的好处,那么什么时候该应用“迪米特法则”呢?又或者说怎样写代码才能满足迪米特法则呢?总结来说就是:

方法不能乱调用,必须调用下面类型的方法才满足“迪米特法则”:

①当前对象的其他方法,即同一个类中的其他方法。

②传递进来的参数对象的方法。

③在方法中自己实例化的任何对象的方法。

④当前对象内部的组件的方法,即当前对象定义的属性对象的方法。

 

 

博客文章版权申明


 

 

 

 


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部