赞
踩
迪米特法则(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()方法是怎样实现的,送奶工并不用关心。这样,代码将会变得更加容易维护,因为顾客类一个小小的改变并不会波及整个源代码,也就是说,其他的代码不用跟着顾客类的变化而一起变化。
现在我们看到了“迪米特法则”带来的好处,那么什么时候该应用“迪米特法则”呢?又或者说怎样写代码才能满足迪米特法则呢?总结来说就是:
方法不能乱调用,必须调用下面类型的方法才满足“迪米特法则”:
①当前对象的其他方法,即同一个类中的其他方法。
②传递进来的参数对象的方法。
③在方法中自己实例化的任何对象的方法。
④当前对象内部的组件的方法,即当前对象定义的属性对象的方法。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。