在《软件工程-实践者的研究方法》的10.2.3和10.2.4两节分别介绍了一些常见的模块内聚和耦合的情况。
内聚
内聚性主要体现在模块功能的专一性上,这意味着一个模块只能封装那些相互关联密切,以及与模块实现功能密切相关的属性和操作。
书中主要提到了以下几种内聚性
1.功能内聚
设计良好的模块只完成一个特定的功能,模块中的元素紧密配合来完成这个功能。
2.分层内聚
将各模块按访问的层次结构组织,高层可以访问低层,当低层不能访问高层。(个人理解,web应用里面的action->service->dao这三层的划分方式可能体现了分层内聚的思想,明显的特征就是action可以访问service层提供的方法,但service层不会知道action层的任何信息)
3.通信内聚
访问相同数据的所有操作都被定义到一个类中。(明显的例子就是DAO类)
耦合性
1.内容耦合
内容耦合可能发生在某个类的public域上,简单的解决方法就是将public域设为private的,然后提供getter/setter和在类外部对域进行修改的方法。
2.共有耦合/外部耦合
我将这两种情况合并都一起讨论了,个人感觉两个很相似。当大量的模块需要使用相同的一些数据时(如一个全局变量,或者一些文件,数据库),如果这个数据发生改变,这意味着所有依赖于它的模块可能也要进行相应的修改。我认为解决的方法可能是不直接使用(访问)这些数据,而是通过一个抽象好的模块(如DAO)来将使用数据的模块和数据访问接口耦合,从而解除各模块和特定数据之间的耦合。比如说你开始使用sqlserver数据库,如果你程序的其它部分不是通过一个抽象,统一的DAO接口来访问数据库,当底层数据库发生变更(比如sqlserver定制性不强,决定使用开源的mysql替换)时,所有使用数据的模块都要进行相应的修改。相反的,如果你使用数据的模块都是通过DAO接口来获取数据时,你需要做的只是提供一个特定于mysql的DAO实现,而使用数据的所有模块不需要任何的变跟。
3.控制耦合
控制耦合发生的在类A调用类B的方法,但B的方法执行的逻辑流程还依赖于A传递的额外控制信息。解决的方法可以是,使用B类的内部状态来替代外部传递进来的控制信息,如使用多态来将if-else变成两个不同的实现类,这样A根据不同的情况持有不同的B的实现类,并调用具体的方法。
4.标记耦合
类B作为类A方法的参数时就发生了这种耦合,最好的方法自然是使用模板。
1 class Printer(){ 2 public void print(Integer a){ 3 System.out.println(a) 4 } 5 }
上面是一打印Integer的方法,如果现在需要支持string类型,上面的程序就显得无能为力,这就是Integer类跟Printer类产生了标记耦合。我们可以使用模板来解决这个问题。
1 class Printer<T>{ 2 public void print(T a){ 3 System.out.println(a) 4 } 5 }
5.例程调用耦合
一个操作调用另一个操作,这样的耦合有时候是必须的。
6.类型使用耦合
类A使用类B定义的数据类型,可以将公用的数据类型单独拿出来形成一个模块。
7.包含导入耦合
有时候也是必须的,合理设计包结构可以最小化耦合的程度。
8.数据耦合
操作传递的参数过多,可以考虑使用DTO来规范传递的数据,减少不必要的数据传输。
以上就是我对内聚和耦合的学习笔记。
参考:
书籍:《软件工程-实践者的研究方法》
他人博客:http://blog.csdn.net/blizmax6/article/details/6756261