迪米特法则又叫最少知识原则,就是说一个对象应当对其他对象有尽可能少的了解。
下面看看迪米特法则的其他表述方法:①只与你直接的朋友通信;②不要与陌生人说话;③每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
一、狭义迪米特法则
如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中的一个类需要调用另一个类的某一个方法的话,可以通过第三者转发这个调用。
狭义迪米特法则的缺点:会在系统里造出大量的小方法,散落在系统的各个角落。这些方法仅仅是传递间接地调用,因此与系统的商务逻辑无关。当设计师试图从一张类图看出总体的架构时,这些小的方法会造成迷惑和困扰。
遵循类之间的迪米特法则会使一个系统的局部设计简化,因为每一个局部都不会和远距离的对象有直接的关联。但是,也会造成系统的不同模块之间的通信效率降低,也会是系统的不同模块之间不容易协调。
与依赖倒转原则的互补使用:为了克服狭义的迪米特法则的缺点,可以使用依赖倒转原则,引入一个抽象的类型引用“抽象陌生人”对象,使“某人”依赖于“抽象陌生人”。换言之,就是将“抽象陌生人”变成朋友。
二、广义的迪米特法则
其实,迪米特法则所谈论的,就是对对象之间的信息流量、流向以及信息的影响控制。在软件系统中,一个模块设计的好不好的最主要、最重要的标志,就是该模块在多大的程度上将自己的内部数据和其他与实现有关的细节隐藏起来。一个设计的好的模块可以将它所有的实现细节隐藏起来,彻底的将提供给外界的API和自己的实现分割开来。这样一来,模块与模块之间就可以仅仅通过彼此的API相互通信,而不理会模块内部的工作细节。这一概念就是“信息的隐藏”,或者叫做“封装”。
信息的隐藏非常重要的原因在于,他可以使各个子系统之间解耦,从而允许他们独立的被开发、优化、使用、阅读以及修改。这种解耦可以有效的加快系统的开发过程,因为可以独立的同事开发各个模块。他可以使维护过程变得容易,因为所有的模块都容易读懂,特别是不必担心对其他模块的影响。
虽然信息的隐藏本身并不能带来更好的性能,但是他可以使性能的有效调整变得容易。一旦确认某一个模块是性能的瓶颈时,设计人员可以针对这个模块本身进行优化,而不必担心影响到其他的模块。信息的隐藏还可以促进软件的复用。由于每一个模块都不依赖于其他模块而存在,因此每一个模块都可以独立地在其他的地方使用。一个系统的规模越大,信息的隐藏就越是重要,而信息隐藏的威力就越明显。
迪米特法则的主要用意是控制信息的过载。在将迪米特法则运用到系统设计中时,要注意下面的几点:
①在类的划分上,应当创建有弱耦合的类。类之间的耦合越弱,就越有利于复用。一个处在弱耦合的类一旦被修改,不会对有关系的类造成波及。
②在类的结构设计上,每一个类都应当尽量降低访问成员的访问权限。换言之,一个类包装好各自的private状态。这样一来,想要了解其中的一个类的意义时,不需要了解很多特别的类的细节。一个类不应当public自己的属性,而应当提供取值和赋值方法让外界间接访问自己的属性。
③在类的设计上,只要有可能,一个类应当设计成不便类。
④对其他类的引用上,一个对象对其他对象的引用应当降低到最低。
三、广义迪米特法则在类设计中的体现
1、优先考虑将一个类设置成不变类
一个对象与外界的通讯大体可以分成两种,一种是改变这个对象的状态的,另一种是不改变这个对象的状态的。如果一个对象的内部状态根本就是不可能改变的,那么他与外界的通信当然大大得了折扣。当涉及任何一个类的时候,都首先考虑这个类的状态是否需要改变。即便一个类必须是可改变类,在给他的属性设置赋值方法的时候,也要保持吝啬的态度。除非真的需要,否则不要为一个属性设置赋值方法。
2、尽量降低一个类的访问权限
在满足一个系统对这个类的需求的同时,应当尽量降低这个类的访问权限。对于顶级的类来说,只有两个可能的访问性等级:
①package-private:这个默认访问权限。如果一个类是package-private的,那么他就只能从当前库访问。
②public:如果一个类是public的,那么这个类从当前库和其他库都可以访问。
一个类具有package-private访问权限的好处是,一旦这个类发生变化,那么受影响的客户端必定都在这个库内部。由于一个软件包往往有他自己的库结构,因此一个访问权限为package-private的类是不会被客户应用程序使用的。这就意味着软件提供商可以自由的决定修改这个类,而不必担心对客户的承诺。
相反,如果一个类被不恰当设置成public,那么客户程序就可能会使用这个类。一旦这个类在一个新版本中被删除,就有可能造成一些客户的程序停止运行的情况。因此,如果一个类可以被设置成package-private的,那么就不应当将他设置成public的。
3、谨慎使用serializable
一个类如果实现了serializable接口的话,客户端就可以将这个类的实例串行化,然后在并行化。由于串行化和并行化涉及到类的内部结构,如果这个类的内部private结构在一个新版本中发生变化的话,那么客户端可能会根据新版本的结构试图将一个老版本的串行化结果并行化,这会导致失败。
换言之,为防止这种情况发生,软件提供商一旦将一个类设置为serializable的,就不能再在新版本中修改这个类的内部结构,包括private的方法和句段。因此,除非绝对必要,不用使用serializable。
4、尽量降低成员的访问权限
类的成员包括属性、方法、嵌套类、嵌套接口等,一个类的成员可以有四种不同的访问权限:private、default、protected、public。作为一条指导原则,在设计一个类的方法时,必须首先考虑将其设置为private,只有当发现当前库中还有别的类需要调用这个方法时,才可能将其访问权限改为default。
对于一个public的类来说,将一个方法从default改为protected或者public,意味着他的访问权限有了巨大的变化。一旦一个方法设置为protected,这个方法就可以被位于另一个库中的子类访问;如果设置成public,那么就可以被任何的类访问。对于一个软件提供商,这就意味着会有客户程序使用这个方法,因此在所有以后的版本中就要承诺不改变这个方法的特征。因此,将private或default方法修改为protected或public,必须慎之又慎。
四、迪米特法则在代码层次上的实现
虽然java语言允许一个变量在任何地方声明,但是一般只在需要一个变量的时候才声明它,这样可以有效的控制局域变量的有效范围。一个变量如果仅仅在块的内部使用的话,就应当将这个变量在块的内部使用的地方声明,而不是放到块的外部或者块的开头声明。这样有两个好处:
①程序员可以很容易读懂程序;②如果一个变量是在需要它的程序块的外部声明的,那么当这个块还没有被执行的时,这个变量就已经分配了内存;而在这个程序块已经执行完毕后,这个变量所占据的内存空间还没有被释放,这显然是不好的。
本文通过总结《java与模式》而来。