08 对象互连-迪米特法则
1. 对象互连
- 研究将各个对象绑定到一起的连接特性
- 一种考虑对象互连的方式就是研究可视性和依赖性这两个概念。
- 可视性描述了关于名称的特性,若通过该名称句柄可以存取对象,且对象的名称是合法的且代表该对象,那么在这个特定环境下该对象就是可见的。
- 描述可视性的相关术语还包括标识符的范畴
- 依赖性将两个对象或者类联系起来,在不存在另外一个对象的条件下,如果一个对象的存在无任何意义,就说该对象依赖于另外那个对象。
例如: 子类几乎总是依赖于它的父类
2. 耦合和内聚
- 耦合(coupling)和内聚(cohesion)的思想提供了一个框架,用于评价对象和类的应用是否有效。
- 耦合描述类之间的关系,内聚描述类内部的关系。
耦合的种类
耦合性是指在一个软件结构中不同模块之间相互依赖的强弱程度
从最差的耦合到较好的耦合:
- 内部数据耦合
- 全局数据耦合
- 控制(或顺序)耦合
- 组件耦合
- 参数耦合
- 子类耦合
内部数据耦合
内部数据耦合发生在当一个类的实例直接修改另外一个类中的本地数据值(实例变量)时。
Class SneakyModifier{
public void sneaky(){
//change my friends name
myFriend.name=“Lucy”;
}
Person myFriend;
}
Class Person{
public Person(){
name=“Larry”;
}
public string name;
}
全局数据耦合
- 全局数据耦合发生在两个或者更多个类型都依赖于公用的全局数据结构而绑定到一起的时候。
Double todaysDow;
Class One{
public:void setDow(){
todayDow=9473; }
}
Class Two{
public:void printDow(){
cout<<“Today the Dow hit”<<todaysDow;
}
};
控制或者顺序耦合
- OO框架中,可代替全局数据耦合的方法是建立新类,负责“理解”数据值,所有全局数值的存取都需经过该类。
- 一个类必须以一种由任何位置控制的特定的顺序来执行操作。
class MyClass {
doFirst() { ... }
doSecond() { ... }
doThird() { ... }
}
组件耦合
组件耦合发生在一个类包含的数据字段或数值为另外一个类的实例时
class Set{
.
.
private List data;
}
参数耦合
参数耦合发生在一个类必须调用另外一个类的服务和例程时,此时两个类之间所发生的唯一关系就是一个类需要为另一个类提供参数数目、类型和返回值类型。
class myClass{
public void doSomething(Set aset){
//do something using the argument value
}
}
子类耦合
子类耦合是面向对象编程所特有的,描述了一个类与其父类之间的关系。通过继承,子类的实例可以被看成父类的实例。
Class Parent{
.
.
}
Class Child extends Parent{
.
.
}
内聚
- 类的(内部)内聚性是该结构中各个元素之间绑定程度的量度。
从最弱的内聚到最强的内聚:
1. 随机内聚:对程序随意划分
- 如果一个模块的各成分之间毫无关系,则称为偶然内聚,也就是说模块完成一组任务,这些任务之间的关系松散,实际上没有什么联系。
Word窗口的工具菜单,在本菜单中,各工具间基本没什么联系。该菜单具有偶然内聚。
在OO中,一个类由多个无关的方法组成。
2. 逻辑内聚:算术函数库
- 几个逻辑上相关的功能被放在同一模块中
一个函数能打印季度开支报告、月份开支报告和日开支报告,具体打印哪一个,将由传入的控制标志决定,该函数具有逻辑内聚性。
3. 时间内聚:如实现程序初始化的类
- 多个元素几乎同时使用而绑定到一起
- 如果一个模块完成的功能必须在同一时间内执行(如系统初始化),但这些功能只是因为时间因素关联在一起
操作系统的开机初始化模块,包含的动作没什么大的关系,但必须在开机后的一段时间内都完成。整个开机初始化模块具有时间内聚。
4. 通信内聚:类扮演数据或设备的管理者
一个类的所有方法由于需要存取相同的“输入/输出”数据或设备而组合到一起时,类扮演数据或设备的管理者
数据或者设备的manager
模块内各功能部分使用了相同的输入数据或产生相同的输出数据
5. 顺序内聚:避免顺序耦合
- 一个类的各个元素由于必须以某种特定顺序被激活而链接到一起
- 如果一个模块的各个成分和同一个功能密切相关,而且一个成分的输出作为另一个成分的输入
某干部退休,模块计算他的离/退休工资:打开干部信息文件;读出文件中他的职务/级别等信息,通过一定算法判断他是否具备离休资格,结论写入文件;再读出文件中他的目前工资、工作年限、是否离休等信息,通过一定算法计算他的离/退休工资,再结果写入文件。该模块操作同一个文件,必须先判断出他是否离休,再计算离/退休工资。整个模块具有顺序内聚。
6. 功能内聚:类中元素通过执行特定功能关联起来
模块的所有成分对于完成单一的功能都是必须的
Int fac(int n){
Int f;
if(n==0,n==1)
f=1;
Else
f=fac(n-1)*n;
return(f);}
/*函数fac,计算n!,本模块功能单一,具备功能内聚*/
7. 数据内聚:类定义一个数据集合
- 模块完成多个功能,各个功能都在同一数据结构上操作,每一项功能有一个唯一的入口点。这个模块将根据不同的要求,确定该模块执行哪一个功能。这个模块的所有功能都是基于同一个数据结构(符号表)
迪米特/迪墨特尔法则(Law of Demeter, LoD )
- 目的:通过限制对象之间的互连来减少其耦合度。
- 也叫最少知识原则(LeastKnowledge Principle, LKP)
- 问题由来:类与类之间的关系越密切,耦合度越大,当一个类发生改变时,对另一个类的影响也越大。
- 解决方案:尽量降低类与类之间的耦合。
- 一个类对自己依赖的类知道的越少越好。
即:对于被依赖的类来说,无论逻辑多么复杂,都尽量地将逻辑封装在类的内部,对外除了提供的public方法,不对外泄漏任何信息。
只与直接的朋友通信
- 朋友:每个对象都会与其他对象有耦合关系,只要两个对象之间有耦合关系,我们就说这两个对象之间是朋友关系。
- 直接的朋友:耦合的方式很多,依赖、关联、组合、聚合等。其中,我们称出现在成员变量、方法参数、方法返回值中的类为直接的朋友。
狭义的迪米特法则
如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用。如果其中一个类需要调用另一类的某一个方法的话,可以通过第三者转发这个调用。
广义的迪米特法则
- 一个模块设计的好坏的一个重要标志就是该模块在多大程度上将自己的内部数据与实现的有关细节隐藏起来。
- 一个软件实体应当尽可能少地与其他实体发生相互作用。
- 每一个软件单位对其他的单位都只有最少的知识,而且局限于那些与本单位密切相关的软件单位。
- 迪米特法则的目的在于降低类与类之间的耦合。由于每个类尽量减少对其他类的依赖,因此,很容易使得系统的功能模块功能独立,使得相互间存在尽可能少的依赖关系。
弊端
- 迪米特法则是一种面向对象系统设计风格的一种法则,尤其适合做大型复杂系统设计指导原则。
- 过度使用迪米特法则,也会造成系统的不同模块之间的通信效率降低,使系统的不同模块之间不容易协调等缺点。
- 同时,因为迪米特法则要求类与类之间尽量不直接通信,如果类之间需要通信就通过第三方转发的方式,这就直接导致了系统中存在大量的中介类,这些类存在的唯一原因是为了传递类与类之间的相互调用关系,这就毫无疑问的增加了系统的复杂度。
END