迪米特法则(LoD),如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
其首先强调的前提是:在类的结构设计上,每一个类都应当尽量降低成员的访问权限。也就是说,一个类包装好自己的Private状态,不需要让别的类知道的字段和行为就不要公开。
其根本思想是:强调了类之间的松耦合。在程序设计中,类之间的耦合越弱,约有利用复用,一个处在弱耦合的类被修改,不会对有关系的类造成波及。也就是说,信息的隐藏促进了软件的复用。
对需求进行抽象,而需求的具体实现使用具体子类。eg:把公司的一个部门抽象成抽象类或借口,这里为了实现某一方面的职能,而这个部门的每一个人都会是一个具体的子类,当需要人员管理调度的时候,由接口(部门类)来实现具体人员的管理和任务分配。
缺点:
会在系统内造出大量的小方法,散落在系统的各个角落.这些方法仅仅是传递间接的调用,因此系统与系统中的商业逻辑无关.当设计师试图从一张类图看出总体的构架时,这些小方法会造成迷惑和困扰.
为了克服狭义迪米特法则的缺点,可以使用依赖倒转原则,引入一个抽象的类型引用"抽象陌生人"对象,使"某人"依赖于"抽象陌生人",换言之,就是将"抽象陌生人"变成朋友.
米特法则不希望类直接建立直接的接触。如果真的有需要建立联系,也希望能通过它的友元类来转达。因此,应用迪米特法则有可能造成的一个后果就是:系统中存在大量的中介类,这些类之所以存在完全是为了传递类之间的相互调用关系-这在一定程度上增加了系统的复杂度。
例如,购房者要购买楼盘A、B、C中的楼,他不必直接到楼盘去买楼,而是可以通过一个售楼处去了解情况,这样就减少了购房者与楼盘之间的耦合,如图10-6所示。
后文中的外观模式(Facade)和中介者模式(Mediator),都是如上这种迪米特法则应用的例子。
☆ 迪米特法则,又叫最少知识原则,就是说,一个对象应当对其他对象有尽可能少的了解。
ξ 11.1 迪米特法则的各种表述
① 只与你直接的朋友们通信;
② 不要跟“陌生人”说话;
③ 每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
ξ 11.2 狭义的迪米特法则
☆ 如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中的一个类需要调用另外一个类的某一个方法,可以通过第三者转发这个调用。
参考下例,Someone、Friend和Stranger三个类。
Someone类有一个方法接受一个Friend类型的变量:
{
public void operation1( Friend friend )
{
Stranger stranger = friend.provide() ;
stranger.operation3() ;
}
}
所以Someone和Friend是朋友类(直接通讯的类)。
同理,Friend类持有一个Stranger类的私有对象,他们是朋友类:
{
private Stranger stranger = new Stranger() ;
public void operation2(){}
public Stranger provide()
{
return stranger ;
}
}
在这里,Someone类和Stranger类不是朋友类,但Someone类却通过Friend类知道了Stranger类的存在,这显然违反迪米特法则。
现在,我们对Someone和Friend类进行重构。首先在Friend类里添加一个方法,封装对Stranger类的操作:
{
private Stranger stranger = new Stranger() ;
public void operation2(){}
public Stranger provide()
{
return stranger ;
}
public void forward()
{
stranger.operation3() ;
}
}
然后,我们重构Someone的operation1方法,让其调用新提供的forward方法:
{
public void operation1( Friend friend )
{
friend.forward() ;
}
}
现在Someone对Stranger的依赖完全通过Friend隔离,这样的结构已经符合狭义迪米特法则了。
仔细观察上述结构,会发现狭义迪米特法则一个明显的缺点:会在系统里造出大量的小方法,散落在系统的各个角落。这些方法仅仅是传递间接的调用,因此与系统的商务逻辑无关,当设计师试图从一张类图看出总体的框架时,这些小的方法会造成迷惑和困扰。遵循迪米特法则会使一个系统的局部设计简化,因为每一个局部都不会和远距离的对象有直接关联。但是,这也会造成系统的不同模块之间的通信效率降低,也会使系统的不同模块之间不容易协调。
结合依赖倒转原则,我们对代码进行如下重构来解决这个问题,首先添加一个抽象的Stranger类,使Someone依赖于抽象的“Stranger”角色,而不是具体实现:
{
abstract void operation3() ;
}
然后,让Stranger从该类继承:
{
public void operation3() {}
}
随后,我们重构Someone使其依赖抽象的Stranger角色:
{
public void operation1( Friend friend )
{
AbstractStranger stranger = friend.provide() ;
stranger.operation3() ;
}
}
最后,我们重构Friend的provide方法,使其返回抽象角色:
{
private Stranger stranger = new Stranger() ;
public void operation2(){}
public AbstractStranger provide()
{
return stranger ;
}
}
现在,AbstractStranger成为Someone的朋友类,而Friend类可以随时替换掉AbstractStranger的实现类,Someone不再需要了解Stranger的内部实现细节。下图是重构后的UML类图:
ξ 11.3 迪米特法则与设计模式
对迪米特法则的最好描述,可以参考门面模式和调停者模式。
ξ 11.4 广义迪米特法则
一个模块设计得好坏的一个重要的标志就是该模块在多大的程度上将自己的内部数据与实现有关的细节隐藏起来.
信息的隐藏非常重要的原因在于,它可以使各个子系统之间脱耦,从而允许它们独立地被开发,优化,使用阅读以及修改.
迪米特法则的主要用意是控制信息的过载.
☆ 在将迪米特法则运用到系统的设计中时,应注意的几点:① 在类的划分上,应该创建有弱耦合的类;
② 在类的结构设计上,每一个类都应当尽量降低成员的访问权限;
③ 在类的设计上,只要有可能,一个类应当设计成不变类;
④ 在对其他类的引用上,一个对象对其它对象的引用应当降到最低;
⑤ 尽量降低类的访问权限;
⑥ 谨慎使用序列化功能;
⑦ 不要暴露类成员,而应该提供相应的访问器(属性)。
尽量限制局部变量的有效范围