原起:
每每看一些Spring、MVC的资料都会看到一些低耦合啊之类的好处,学习时也经常听到关于高内聚、低耦合,但总感觉太模糊,自己也说不出个所以然来,故查阅资料,总结了一些自己能够理解的东西做个简短记录,方便日后查阅、修改、提升。
定义:
耦合是程序模块之间的联系,是对某元素与其它元素之间的连接、感知和依赖的度量
内聚是程序模块内部的联系,是对软件系统中元素职责相关性和集中度的度量
这里就需要首先明确一下元素指的是什么,我理解这里的模块即可以是功能(方法)、对象(类),也可以指系统、子系统、模块等。后面整理具体的例子时可能理解的更深刻,下面分别讨论一下内聚、耦合。
内聚
什么是内聚:
内聚简单来说就是单一职责,不要挂羊头卖狗肉,具有高内聚性的一个元素,只完成它职责内的事情,而把那些不在它职责内的事情拿去请求别人来完成。就好像我是一个开发人员,当遇到软件测试的问题,就会找一个测试人员来做,而我只去做开发~~~
内聚的种类:
它们之间的内聚度由弱到强排列如下:(引用)
1、偶然内聚:一个模块内的各处理元素之间没有任何联系,只是偶然地被凑到一起。这种模块也称为巧合内聚,内聚程度最低。
2、逻辑内聚:这种模块把几种相关的功能组合在一起, 每次被调用时,由传送给模块参数来确定该模块应完成哪一种功能 。
3、时间内聚:把需要同时执行的动作组合在一起形成的模块称为时间内聚模块。
4、过程内聚:构件或者操作的组合方式是,允许在调用前面的构件或操作之后,马上调用后面的构件或操作,即使两者之间没有数据进行传递。简单的说就是如果一个模块内的处理元素是相关的,而且必须以特定次序执行则称为过程内聚。
5、通信内聚:指模块内所有处理元素都在同一个数据结构上操作或所有处理功能都通过公用数据而发生关联(有时称之为信息内聚)。即指模块内各个组成部分都使用相同的数据数据或产生相同的数据结构。
6、顺序内聚:一个模块中各个处理元素和同一个功能密切相关,而且这些处理必须顺序执行,通常前一个处理元素的输出时后一个处理元素的输入。
例如某模块完成工业产值求值的功能,前一个功能元素求总产值,后一个功能元素求平均产值,显然该模块内两部分紧密关联。
顺序内聚的内聚度比较高,但缺点是不如功能内聚易于维护。
功能内聚:模块内所有元素的各个组成部分全部都为完成同一个功能而存在,共同完成一个单一的功能,模块已不可再分。即模块仅包括为完成某个功能所必须的所有成分,这些成分紧密联系、缺一不可。
为什么要高内聚:
关于项目为什么要高内聚呢?我觉得可以从可读性、复用性、可维护性和易变更性四个方面来理解。(引用)
可读性:一个人写文章、讲事情,条理清晰才能易于理解,这同样发生在读写软件代码上。如果一堆代码写得一团乱麻,东一个跳转西一个调用,读它的人会感觉非常头疼。这种事情也许一直在写程序的你我都曾经有过经历。如果一段程序条理非常清晰,每个类通过名称或说明都能清楚明白它的意义,类的每个属性、函数也都是易于理解的它所应当完成的任务和行为,这段程序的可读性必然提高。在软件产业越来越密集,软件产业中开发人员协作越来越紧密、分工越来越细的今天,软件可读性的要求相信也越来越为人们所重视。
复用性:在软件开发中,最低等级的复用是代码拷贝,然后是函数的复用、对象的复用、组件的复用。软件开发中最懒的人是最聪明的人,他们总是想到复用。在代码编写的时候突然发现某个功能是曾经实现过的功能,直接把它拷贝过来就ok了。如果这段代码在同一个对象中,那么就提出来写一个函数到处调用就行了。如果不是在同一个对象中呢,就将其抽象成一个对象到处调用吧。如果不在一个项目中呢,那就做成组件给各个项目引用吧。代码复用也使我们的代码在复用的过程中不断精化、不断健壮、提高代码质量。代码的复用的确给我们的开发带来了不少便利,但是一段代码能否在各个需要的地方都能复用呢?这给我们的软件开发质量提出了新的要求:好的代码可以复用,不好的则不行。软件中的一个对象如果能保证能完成自己职能范围内的各项任务,同时又不去理会与自己职能无关的其它任务,那么它就能够保证功能的相对独立性,也就可以脱离自己所处的环境而复用到其它环境中,这是一个具有内聚性的对象。
可维护性和易变更性 :我们现在的软件是在不断变更的,这种变更不仅来自于我们的客户,更来自于我们的市场。如果我们的软件通过变更能及时适应我们的市场需求,我们就可以在市场竞争中获胜。如何能及时变更以适应我们的市场呢,就是通过调整软件的结构,使我们每次的变更付出的代价最小,耗费的人力最小,这种变更才最快最经济。高内聚的软件,每个系统、模块、类的任务都高度相关,就使每一次的变更涉及的范围缩小到最小。比如评审表发生了变更,只会与评审表对象有关,我们不会去更改其它的对象。如果我们能做到这一点,我们的系统当然是可维护性好、易变更性好的系统。
总结来说高内聚有可读性强、易维护和变更、支持低耦合、移植和重用性强等好处。
如何做到高内聚:
耦合
什么是耦合:
内聚指的是元素内部的事,而耦合就是元素与元素之间的关系,当A元素发生变动会影响到B元素,那么他们之间就存在耦合关系。一个元素A去连接元素B,或者通过自己的方法可以感知B,或者当B不存在的时候就不能正常工作,那么就说元素A与元素B耦合。例如,写一个类B会依赖于类A,当改变类A的某些属性或者方法会使得你不得不同时改变类B,这是类A、B就存在耦合关系。 类与类之间的关系越来越密切,耦合度越来越大,当一个类发生改变时,对另外一个类的影响也越大。
耦合的种类:
内容耦合:
公共耦合:若一组模块都访问同一个公共数据环境,则它们之间的耦合就称为公共耦合。公共的数据环境可以是全局数据结构、共享的通信区、内存的公共覆盖区等。
外部耦合: 一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,则称之为外部耦合。
控制耦合:如果一个模块通过传送开关、标志、名字等控制信息,明显地控制选择另一模块的功能,就是控制耦合。
标记耦合:一组模块通过参数表传递记录信息,就是标记耦合。这个记录是某一数据结构的子结构,而不是简单变量。
数据耦合:一个模块访问另一个模块时,彼此之间是通过简单数据参数 (不是控制参数、公共数据结构或外部变量) 来交换输入、输出信息的。
为什么要低耦合:
可以想象一下,一个程序有50个函数,这个程序执行得非常好,然而一旦你修改其中一个函数,其他49个函数都需要做修改,这就是高耦合的后果。低耦合性程序的可读性及可维护性会比较好。低耦合就是要求在我们的软件系统中,某元素不要过度依赖于其它元素。请注意这里的“过度”二字。因为我们的系统基本不可能做到所有模块之间没有任何关联,几遍一个Java类,如果不和jdk耦合依然无法工作。
如何做到低耦合:
首先说明一下各大框架的低耦合特性,比如,使用struts我们可以应用MVC模型,使页面展现与业务逻辑分离,做到了页面展现与业务逻辑的低耦合。当我们的页面展现需要变更时,我们只需要修改我们的页面,而不影响我们的业务逻辑;同样,我们的业务逻辑需要变更的时候,我们只需要修改我们的java程序,与我们的页面无关。使用spring我们运用IoC(反向控制),降低了业务逻辑中各个类的相互依赖。假如类A因为需要功能F而调用类B,在通常的情况下类A需要引用类B,因而类A就依赖于类B了,也就是说当类B不存在的时候类A就无法使用了。使用了IoC,类A调用的仅仅是实现了功能F的接口的某个类,这个类可能是类B,也可能是另一个类C,由spring的配置文件来决定。这样,类A就不再依赖于类B了,耦合度降低,重用性提高了。使用hibernate则是使我们的业务逻辑与数据持久化分离,也就是与将数据存储到数据库的操作分离。我们在业务逻辑中只需要将数据放到值对象中,然后交给hibernate,或者从hibernate那里得到值对象。至于用Oracle、MySQL还是SQL Server,如何执行的操作,与我无关。
但是仅仅依靠框架提供的降低软件耦合的方法是远远不够的,一下列出一些常用的降低耦合性的操作
少使用类的继承,多用接口隐藏实现的细节。 Java面向对象编程引入接口除了支持多态外, 隐藏实现细节也是其中一个目的。
模块的功能化分尽可能的单一,道理也很简单,功能单一的模块供其它模块调用的机会就少。(其实这是高内聚的一种说法)。
遵循一个定义只在一个地方出现。
少使用全局变量。
类属性和方法的声明少用public,多用private关键字。
多用设计模式,比如采用MVC的设计模式就可以降低界面与业务逻辑的耦合度。
尽量不用“硬编码”的方式写程序,同时也尽量避免直接用SQL语句操作数据库。
最后当然就是避免直接操作或调用其它模块或类(内容耦合);
如果模块间必须存在耦合,原则上尽量使用数据耦合,少用控制耦合,
限制公共耦合的范围,避免使用内容耦合。
高内聚低耦合
但说到这里真的是内聚性越高越好,耦合性越低越好吗?答案当然是否定的!
最强的内聚莫过于一个类只写一个函数,这样内聚性绝对是最高的。但这会带来一个明显的问题:类的数量急剧增多,这样就导致了其它类的耦合特别多,于是整个设计就变成了“高内聚高耦合”了。由于高耦合,整个系统变动同样非常频繁。
对于耦合来说,最弱的耦合是一个类将所有的函数都包含了,这样类完全不依赖其它类,耦合性是最低的。但这样会带来一个明显的问题:内聚性很低,于是整个设计就变成了“低耦合低内聚”了。由于低内聚,整个类的变动同样非常频繁。
所以强内聚和弱耦合是相辅相成的,一个良好的设计是由若干个强内聚模块以弱耦合的方式组装起来的(引用)
再简单一点来说吧, 高内聚、低耦合讲的是程序单位协作的问题,
你可以这样理解,一个企业的管理,
最理想的情况就是各个部门各司其职,井然有序,互不干涉,
但是需要沟通交流的时候呢,
各个部门都可以找到接口人专门负责部门沟通以及对外沟通。
在软件里呢, 就是说各个模块要智能明确, 一个功能尽量由一个模块实现,
同样,一个模块最好只实行一个功能。这个是所谓的“内聚”;
模块与模块之间、系统与系统之间的交互,是不可避免的,
但是我们要尽量减少由于交互引起的单个模块无法独立使用或者无法移植的情况发生,
尽可能多的单独提供接口用于对外操作, 这个就是所谓的“低耦合”。
但是实际的设计开发过程中,总会发生这样那样的问题与情况,
真正做到高内聚、低耦合是很难的,很多时候未必一定要这样,
更多的时候“最适合”的才是最好的,
不过,理解思想,审时度势地使用,
融会贯通,灵活运用,才是设计的王道。