转载自:http://www.jianshu.com/p/14589fb6978e (作者简书:涅槃1992)
揭秘迪米特法则
迪米特法则(Law of demeter,缩写是LOD)要求:一个对象应该对其他对象保持最少了解, 通缩的讲就是一个类对自己依赖的类知道的越少越好,也就是对于被依赖的类,向外公开的方法应该尽可能的少。
迪米特法则还有一种解释:Only talk to your immediate friends,即只与直接朋友通信.首先来解释编程中的朋友:两个对象之间的耦合关系称之为朋友,通常有依赖,关联,聚合和组成等.而直接朋友则通常表现为关联,聚合和组成关系,即两个对象之间联系更为紧密,通常以成员变量,方法的参数和返回值的形式出现.
那么为什么说是要与直接朋友通信呢?观察直接朋友出现的地方,我们发现在直接朋友出现的地方,大部分情况下可以接口或者父类来代替,可以增加灵活性. (需要注意,在考虑这个问题的时候,我们只考虑新增的类,而忽视java为我们提供的基础类.)
实例演示
不难发现,迪米特法则强调了一下两点:
- 第一要义:从被依赖者的角度来说:只暴露应该暴露的方法或者属性,即在编写相关的类的时候确定方法/属性的权限
- 第二要义:从依赖者的角度来说,只依赖应该依赖的对象
先来解释第一点,我们使用计算机来说明,以关闭计算机为例:
当我们按下计算机的关机按钮的时候,计算机会执行一些列的动作会被执行:比如保存当前未完成的任务,然后是关闭相关的服务,接着是关闭显示器,最后是关闭电源,这一系列的操作以此完成后,计算机才会正式被关闭。
现在,我们来用简单的代码表示这个过程,在不考虑迪米特法则情况下,我们可能写出以下代码
//计算机类
public class Computer{public void saveCurrentTask(){//do something}public void closeService(){//do something}public void closeScreen(){//do something}public void closePower(){//do something}public void close(){saveCurrentTask();closeService();closeScreen();closePower();}
}//人
public class Person{private Computer c;...public void clickCloseButton(){//现在你要开始关闭计算机了,正常来说你只需要调用close()方法即可,//但是你发现Computer所有的方法都是公开的,该怎么关闭呢?于是你写下了以下关闭的流程: c.saveCurrentTask();c.closePower();c.close();//亦或是以下的操作 c.closePower();//还可能是以下的操作c.close();c.closePower();}}
发现上面的代码中的问题了没?
我们观察clickCloseButton()方法,我们发现这个方法无法编写:c是一个完全暴露的对象,其方法是完全公开的,那么对于Person来说,当他想要执行关闭的时候,却发现不知道该怎么操作:该调用什么方法?靠运气猜么?如果Person的对象是个不按常理出牌的,那这个Computer的对象岂不是要被搞坏么?
迪米特法则第一要义
现在我们来看看迪米特法则的第一点:从被依赖者的角度,只应该暴露应该暴露的方法。那么这里的c对象应该哪些方法应该是被暴露的呢?很显然,对于Person来说,只需要关注计算机的关闭操作,而不关心计算机会如何处理这个关闭操作,因此只需要暴露close()
方法即可。
那么上述的代码应该被修改为:
//计算机类
public class Computer{private void saveCurrentTask(){//do something}private void closeService(){//do something}private void closeScreen(){//do something}private void closePower(){//do something}public void close(){saveCurrentTask();closeService();closeScreen();closePower();}
}//人
public class Person{private Computer c;...public void clickCloseButton(){c.close();}}
看一下它的类图:
接下来,我们继续来看迪米特法则的第二层含义:从依赖者的角度来说,只依赖应该依赖的对象。 这句话令人有点困惑,什么叫做应该依赖的对象呢?我们还是用上面“关闭计算机”的例子来说明: 准确的说,计算机包括操作系统和相关硬件,我们可以将其划分为System对象和Container对象。当我们关闭计算机的时候,本质上是向操作系统发出了关机指令,而实则我们只是按了一下关机按钮,也就是我们并没有依赖System的对象,而是依赖了Container。这里Container就是我们上面所说的直接朋友---只和直接朋友通信.
我们就这点,继续深入讨论一下: only talk to your immedate friends
这句话只说明了要和直接朋友通信,但是我觉得这还不完整,我更愿意将其补充为: make sure your friends,only talk to your immedate friends,don't speak to strangers. 大意是:确定你真正的朋友,并只和他们通信,并且不要和陌生人讲话.这样做有个很大的好处就是,能够简化对象与对象之间的通信,进而减轻依赖,提供更高的灵活性,当然也可以提供一定的安全性.
现在来想想现实世界中的这么一种情况:你是一个令人瞩目的公众人物,周围的每个人都知道你的名字,当你独自走在大街上的时候会是怎么样的一种场景?每个人都想要和你聊天!,和你交换信息!!接着,你发现自己已经寸步难行了.如果这时候你有一个经纪人,来帮你应对周围的人,而你就只和这个经纪人通信,这样就大大减轻了你的压力,不是么?此时,这个经济人就相当于你的直接朋友.
迪米特法则第二要义
现在,我们再回顾"关机计算机"这个操作,前面的代码只是遵从了"暴漏应该暴漏的方法"这一点,现在我们结合第二点来进行改进:System和Container相比,System并非Person的直接朋友,而Container才是(Person直接打交道的是Container).因此我们需要将原有的Computer拆分成System和Cotainer,然后使Person只与Container通信,因此代码修改为:
//操作系统
public class System{private void saveCurrentTask(){//do something}private void closeService(){//do something}private void closeScreen(){//do something}private void closePower(){//do something}public void close(){saveCurrentTask();closeService();closeScreen();closePower();}
}//硬件设备容器
public class Container{private System mSystem;public void sendCloseCommand(){mSystem.close();}
}//人
ublic class Person{private Container c;....public void clickCloseButton(){c.sendCloseCommand();}}
来看一下它的类图:
对比这两种方案,明显这种方案二的解耦程度更高,灵活大大增强.不难发现,这应用了我们前面提到的依赖倒置,即面向接口编程.
除此之外,我们发现随着不断的改进,类的数量也在不断的增加,从2个增加到5个,这意味着为了解耦和提高灵活性通常要编写的类的数量会翻倍.因此,你需要在这做一个权衡,切莫刻意为了追求设计,而导致整个系统非常的冗余,最终可能得不偿失.
总结
有人会觉得Container像是一个中介(代理).没错,我们确实可以称其为中介,但这并不能否认他是我们的直接朋友:在很多情况下,中介可以说是我们的一种代表,因此将其定义为直接朋友是没有任何问题的.比如,当你想要租房的时候,你可以找房屋中介,对方会按照你的标准为你寻找合适的住房.但是问题来了:那么做一件事情需要多少中介呢?总不能是我委托一个中介A帮我找房子,但中介A又委托了中介B,中介B又委托了中介C....等等,如果真的是这样,那还不如我自己去找房子效率更高.在实际开发中,委托的层次要控制在6层以下,多余6层以上的会使得系统过分的冗余和并切会委托层次过多而导致开发人员无法正确的理解流程,产生风险的可能会大大提高.
到目前,我们已经彻底的了解了迪米特法则.