当前位置:   article > 正文

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

it 设置原则 最小知道

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

 

什么是“迪米特法则”


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

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

 

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


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

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

 

模拟场景


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

  1. package test;
  2. public class Customer {
  3. private String name;//客户姓名
  4. private Wallet Wallet;//客户的钱包
  5. public Customer(String name, test.Wallet wallet) {
  6. this.name = name;
  7. Wallet = wallet;
  8. }
  9. public String getName() {
  10. return name;
  11. }
  12. public Wallet getWallet() {
  13. return Wallet;
  14. }
  15. }

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

  1. package test;
  2. public class Wallet {
  3. private float value;//钱包内的价值
  4. public Wallet(float value) {
  5. this.value = value;
  6. }
  7. /**
  8. * @return 返回当前钱包内的总额
  9. */
  10. public float getTotalMoney() {
  11. return value;
  12. }
  13. /**
  14. * @param deposit 向钱包中加入的金额数量
  15. */
  16. public void addMoney(float deposit) {
  17. value += deposit;
  18. }
  19. /**
  20. * @param debit 需要从钱包中扣除的金额数量
  21. */
  22. public void subtractMoney(float debit) {
  23. value -= debit;
  24. }
  25. }

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

  1. package test;
  2. public class MilkMan {
  3. /**
  4. * 送奶工执行“交易”
  5. *
  6. * @param payment 送奶工应收取的费用
  7. * @param customer 被收取费用的客户对象
  8. */
  9. public void makeDeal(float payment, Customer customer) {
  10. Wallet wallet = customer.getWallet();
  11. //获取客户的钱包
  12. if (wallet.getTotalMoney() >= payment) {
  13. wallet.subtractMoney(payment);
  14. //从客户的钱包中减去相应的金额
  15. System.out.println("交易产生:送奶工从钱包中成功拿走" + payment + "元");
  16. }
  17. }
  18. }

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

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

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

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

 

哪里出问题了?


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

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

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

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

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

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

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

 

“迪米特法则”的威力


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

新的顾客(Customer2)类:

  1. package test;
  2. public class Customer2 {
  3. private String name;//客户姓名
  4. private Wallet wallet;//客户的钱包
  5. public Customer2(String name, test.Wallet wallet) {
  6. this.name = name;
  7. wallet = wallet;
  8. }
  9. /**
  10. * 由送奶工获取顾客的钱包变成顾客自己使用钱包支付费用
  11. *
  12. * @param bill 顾客支付的费用
  13. * @return 支付的费用
  14. */
  15. public float getPayment(float bill) {
  16. if (wallet != null) {
  17. if (wallet.getTotalMoney() > bill) {
  18. wallet.subtractMoney(bill);
  19. }
  20. }
  21. return bill;
  22. }
  23. public String getName() {
  24. return name;
  25. }
  26. public test.Wallet getWallet() {
  27. return wallet;
  28. }
  29. }

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

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

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

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

运行后的结果为:

 

为什么变好了?


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

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

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

“迪米特”大法好


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

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

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

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

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

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

 

 

博客文章版权申明


 

 

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/488484
推荐阅读
相关标签
  

闽ICP备14008679号