从普通程序员到高级IT工程师必经之路----了解“高内聚,低耦合,代码复用

来源:互联网 发布:js屏蔽运营商广告 编辑:程序博客网 时间:2024/06/05 22:57

一个优秀软件开发人员的必修课:高内聚

一个重要的模式:高内聚。

 高内聚(High Cohesion

高 内聚是另一个普遍用来评判软件设计质量的标准。内聚,更为专业的说法叫功能内聚,是对软件系统中元素职责相关性和集中度的度量。如果元素具有高度相关的职 责,除了这些职责内的任务,没有其它过多的工作,那么该元素就具有高内聚性,反之则为低内聚性。高内聚要求软件系统中的各个元素具有较高的协作性,因为在 我们在完成软件需求中的一个功能,可能需要做各种事情,但是具有高内聚性的一个元素,只完成它职责内的事情,而把那些不在它职责内的事情拿去请求别人来完 成。这就好像,如果我是一个项目经理,我的职责是监控和协调我的项目各个阶段的工作。当我的项目进入需求分析阶段,我会请求需求分析员来完成;当我的项目 进入开发阶段,我会请求软件开发人员来完成;当我的项目需要测试的时候,我会请求测试人员。。。。。。如果我参与了开发,我就不是一个高内聚的元素,因为 开发不是我的职责。我们的项目为什么要高内聚呢?我觉得可以从可读性、复用性、可维护性和易变更性四个方面来理解。

1.可读性

一 个人写文章、讲事情,条理清晰才能易于理解,这同样发生在读写软件代码上。如果一堆代码写得一团乱麻,东一个跳转西一个调用,读它的人会感觉非常头疼。这 种事情也许一直在写程序的你我都曾经有过经历。如果一段程序条理非常清晰,每个类通过名称或说明都能清楚明白它的意义,类的每个属性、函数也都是易于理解 的它所应当完成的任务和行为,这段程序的可读性必然提高。在软件产业越来越密集,软件产业中开发人员协作越来越紧密、分工越来越细的今天,软件可读性的要 求相信也越来越为人们所重视。

2.复用性

在软件开发中,最低等级的复用是代码拷贝,然后是函数的复用、对象的复用、组件 的复用。软件开发中最懒的人是最聪明的人,他们总是想到复用。在代码编写的时候突然发现某个功能是曾经实现过的功能,直接把它拷贝过来就ok了。如果这段 代码在同一个对象中,那么就提出来写一个函数到处调用就行了。如果不是在同一个对象中呢,就将其抽象成一个对象到处调用吧。如果不在一个项目中呢,那就做 成组件给各个项目引用吧。代码复用也使我们的代码在复用的过程中不断精化、不断健壮、提高代码质量。代码的复用的确给我们的开发带来了不少便利,但是一段 代码能否在各个需要的地方都能复用呢?这给我们的软件开发质量提出了新的要求:好的代码可以复用,不好的则不行。软件中的一个对象如果能保证能完成自己职 能范围内的各项任务,同时又不去理会与自己职能无关的其它任务,那么它就能够保证功能的相对独立性,也就可以脱离自己所处的环境而复用到其它环境中,这是 一个具有内聚性的对象。

3.可维护性和易变更性

在前面《如何在struts+spring+hibernate的框架下 构建低耦合高内聚的软件》中我提到,我们现在的软件是在不断变更的,这种变更不仅来自于我们的客户,更来自于我们的市场。如果我们的软件通过变更能及时适 应我们的市场需求,我们就可以在市场竞争中获胜。如何能及时变更以适应我们的市场呢,就是通过调整软件的结构,使我们每次的变更付出的代价最小,耗费的人 力最小,这种变更才最快最经济。高内聚的软件,每个系统、模块、类的任务都高度相关,就使每一次的变更涉及的范围缩小到最小。比如评审表发生了变更,只会 与评审表对象有关,我们不会去更改其它的对象。如果我们能做到这一点,我们的系统当然是可维护性好、易变更性好的系统。

那么,我们如何做 到高内聚呢?就拿前面我提到的评审项目举例。我现在要为“评审表”对象编写一段填写并保存评审表的代码。评审表对象的职责是更新和查询评审表的数据,但是 在显示一个要填写的评审表的时候,我需要显示该评审计划的名称、该评审计划有哪些评审对象需要评审。现在我如何编写显示一个要填写的评审表的代码?我在评 审表对象的这个相应的函数中编写一段查询评审计划和评审对象的代码吗?假如你这样做了,你的代码就不是高内聚的,因为查询评审计划和评审对象的数据不是它 的职责。正确的方法应当去请求“评审计划”对象和“评审对象”对象来完成这些工作,而“评审表”对象只是获取其结果。

另外,如果一个对象 要完成一个虽然在自己职责范围内,但过程非常复杂的任务时,也应当将该任务分解成数个功能相对独立的子函数来完成。我曾经看见一个朋友写的数百行的一个函 数,让人读起来非常费劲。同时这样的函数中一些相对独立的代码,本可以复用到其它代码中,也变成了不可能。所以我给大家的建议是,不要写太长的函数,超过 一百行就可以考虑将一些功能分解出去。

与“低耦合”一样,高内聚也不是一个绝对,而是一个相对的指标,应当适当而不能过度。正如我们在现 实生活中,如果在一个十来人的小公司,每个人的分工可能会粗一些,所分配的职责会广一些杂一些,因为其总体的任务少;而如果在一个一两百人的大公司,每个 人的分工会细一些,所分配的任务会更加专一些,因为总体任务多,更需要专业化的分工来提高效率。软件开发也是一样,如果“评审计划”对象完成的业务功能 少,并且不复杂,它完全可以代理它的子表“评审对象”和“评审者”的管理。但是“评审计划”对象需要完成的“对评审计划表的管理”这个基本职责包含的业务 功能繁多或者复杂,它就应当将“对评审对象表的管理”交给“评审对象”对象,将“对评审者表的管理”交给“评审者”对象。同样,高内聚的可维护性好、易变 更性好只能是一个相对的指标。如果一个变更的确是大范围的变更,你永远不可能通过内聚就不进行大范围的变更了。同时内聚也是要付出代价的,所以你也不必要 去为了一个不太可能的变更去进行过度设计,应当掌握一个度。过度的内聚必将增加系统中元素之间的依赖,提高耦合度。所以“高内聚”与“低耦合”是矛盾的, 必须权衡利弊,综合地去处理。在李洋等人翻译的《UML和模式应用》中,将内聚和耦合翻译为软件工程中的阴与阳,是中国人对内聚和耦合的最佳解释。

综上所述,“高内聚”给软件项目带来的优点是:可读性强、易维护和变更、支持低耦合、移植和重用性强。

一个优秀软件开发人员的必修课:GRASP2)低耦合

关键字设计模式      

我 偶然在googleyahoo这样的搜索引擎搜索GRASP发现,除了国外的网站,国内网站多介绍和讨论GoF而很少介绍GRASP,即使这少量的文章 也讲解非常粗略。个人认为作为优秀的开发人员,理解GRASPGoF更重要,故写此文章。前面我在《(原创)一个优秀软件开发人员的必修课:GRASP 软件开发模式浅析》中介绍了使用GRASP的目的,今天允许我调换一下顺序,先从低耦合讲起,因为诸如创建者模式、信息专家模式的根本目的就是降低耦合。

1.    低耦合(Low Coupling

“低 耦合”这个词相信大家已经耳熟能详,我们在看spring的书籍、MVC的数据、设计模式的书籍,无处不提到“低耦合、高内聚”,它已经成为软件设计质量 的标准之一。那么什么是低耦合?耦合就是对某元素与其它元素之间的连接、感知和依赖的量度。这里所说的元素,即可以是功能、对象(类),也可以指系统、子 系统、模块。假如一个元素A去连接元素B,或者通过自己的方法可以感知B,或者当B不存在的时候就不能正常工作,那么就说元素A与元素B耦合。耦合带来的 问题是,当元素B发生变更或不存在时,都将影响元素A的正常工作,影响系统的可维护性和易变更性。同时元素A只能工作于元素B存在的环境中,这也降低了元 素A的可复用性。正因为耦合的种种弊端,我们在软件设计的时候努力追求“低耦合”。低耦合就是要求在我们的软件系统中,某元素不要过度依赖于其它元素。请 注意这里的“过度”二字。系统中低耦合不能过度,比如说我们设计一个类可以不与JDK耦合,这可能吗?除非你不是设计的Java程序。再比如我设计了一个 类,它不与我的系统中的任何类发生耦合。如果有这样一个类,那么它必然是低内聚(关于内聚的问题我随后讨论)。耦合与内聚常常是一个矛盾的两个方面。最佳 的方案就是寻找一个合适的中间点。

哪些是耦合呢?

1.元素B是元素A的属性,或者元素A引用了元素B的实例(这包括元素A调用的某个方法,其参数中包含元素B)。

2.元素A调用了元素B的方法。

3.元素A直接或间接成为元素B的子类。

4.元素A是接口B的实现。

幸 运的是,目前已经有大量的框架帮助我们降低我们系统的耦合度。比如,使用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那里得到值对象。至于用OracleMySQL还是SQL Server,如何执行的操作,与我无关。

但是,作为优秀的开发人员,仅仅依靠框架提供的降低软件耦合的方法是远远不够的。根据我的经验,以下一些问题我们应当引起注意:

1)   根据可能的变化设计软件

我 们采用职责驱动设计,设计中尽力做到“低耦合、高内聚”的一个非常重要的前提是,我们的软件是在不断变化的。如果没有变化我们当然就不用这么费劲了;但是 如果有变化,我们希望通过以上的设计,使我们在适应或者更改这样的变化的时候,付出更小的代价。这里提供了一个非常重要的信息是,我们努力降低耦合的是那 些可能发生变更的地方,因为降低耦合是有代价的,是以增加资源耗费和代码复杂度为代价的。如果系统中某些元素不太可能变更,或者降低耦合所付出的代价太 大,我们当然就应当选择耦合。有一次我试图将我的表现层不依赖于struts,但发现这样的尝试代价太大而失去意义了。对于软件可能变更的部分,我们应当 努力去降低耦合,这就给我们提出一个要求是,在软件设计的时候可以预判日后的变化。根据以往的经验我认为,一个软件的业务逻辑和采用的技术框架往往是容易 变化的2个方面。客户需求变更是我们软件设计必须考虑的问题。在RUP的开发过程中,为什么需要将分析设计的过程分为分析模型和设计模型,愚以为,从分析 模型到设计模型的过程实际上是系统从满足直接的客户需求到优化系统结构、适应可预见的客户需求变更的一个过程。这种客户需求的变更不仅仅指对一个客户需求 的变更,更是指我们的软件从适应一个客户需求到适应更多客户需求的过程。另一个方面,现在技术变更之快,EJBhibernatespring、 ajax,一个一个的技术像走马灯一样从我们脑海中滑过,我们真不知道明天我在用什么。在这样的情况下,适应变化就是我们最佳的选择。

 

2)   合理的职责划分

 

合 理的职责划分,让系统中的对象各司其职,不仅是提高内聚的要求,同时也可以有效地降低耦合。比如评审计划BUS、评审表BUS、评审报告BUS都需要通过 评审计划DAO去查询一些评审计划的数据,如果它们都去直接调用评审计划DAO(如图A),则评审计划BUS、评审表BUS、评审报告BUS三个对象都与 评审计划DAO耦合,评审计划DAO一旦变更将与这三个对象都有关。在这个实例中,实际上评审计划BUS是信息专家(关于信息专家模式我将在后面讨论), 评审表BUS和评审报告BUS如果需要获得评审计划的数据,应当向评审计划BUS提出需求,由评审计划BUS提供数据(如图B)。经过这样的调整,系统的 耦合度就降低了。

 

3)   使用接口而不是继承

 

通过对耦合的分析,我们不难发现,继承就是一种耦合。如果子类A继承了父 类B,不论是直接或间接的继承,子类A都必将依赖父类B。子类A必须使用在存在父类B的环境中,父类B不存在子类A就不能使用,这样将影响子类A的可移植 性。一旦父类B发生任何变更,更改或去掉一个函数名,或者改变一个函数的参数,都将导致子类A不得不变更,甚至重写。假如父类B的子类数十上百个,甚至贯 穿这个项目各个模块,这样的变更是灾难性的。这种情况最典型的例子是我们现在使用hibernatespring设计DAO对象的方式,具体的描述参见 我写的《如何在 struts + spring + hibernate的框架下构建低耦合高内聚的软件结构》一文。

 

总之,“低耦合”给软件项目带来的优点是:易于变更、易于重用。

 

对于开发者而言,耦合原则表示程序中单个的模块应该尽可能的独立。

 

处理一个模块时,不应该依赖另一个模块的内部工作。

 

内聚原则是指,在一个给定的模块内部,所有的代码应该只完成一个单个的目标。

 

IT界有一句很著名的口号:强内聚、松耦合。

 

即 使是最初级的程序员,在常常的被教导中,他也了解了这句口号的含义:我们的程序要模块化,模块要完成明确的一组关联的服务功能,要求它的各部分是相关的、 有机组合起来是完整体(外部程序来看黑盒子),模块的内部各成分之间相关联程度要尽可能高(强内聚);而模块与模块之间又要求是可分拆的、少依赖的(松耦 合)。

 

人们易于实现强内聚的模块,例如:一个函数实现一个独立的功能,这就是强内聚。

 

人们不易实现松耦合,因为,孤 独的模块毫无意义,只有模块间的相互协调地工作,才能实现系统的目的。而对于模块间的相互关系的设计,没有一定的经验是难以把握。耦合的强度依赖于: (1)一个模块对另一个模块的调用;(2)一个模块向另一个模块传递的数据量;(3)一个模块施加到另一个模块的控制的多少;(4)模块之间接口的复杂程 度。等等。

 

当然,“强内聚、松耦合”也是有矛盾的,如:内聚性越强,则要求的函数越多(每个函数只作一件“事”),这样,将它们组合成 “大”的功能,也就越复杂,就不可能达到松耦合。因此,应在二者之间作出平衡与折衷的选择,这也体现程序员的水平。从系统论的角度来看,系统是有层次的, 即系统可以分为子系统,模块可分为子模块,“强内聚、松耦合”的“度”的把握,应结合系统的次层性来考虑,即通常应在层次性上作出折衷,如:模块内子程序 (下一个层次上)应共享数据(有一定的耦合度),而减少全局变量能降低子程序性间的耦合性。

 

面向对象的语言进一步强化了“强内聚、松耦 合”,类的封装性既强调了相关内容(数据及其操作)的内聚,又强调了类的独立性和私密性。而类的继承性以及友元等,就是在松耦合的原则下规范了类之间的关 联关系。类与类之间通常通过接口的契约实现服务提供者/服务请求者模式,这就是典型的松耦合。

 

“强内聚、松耦合”对于程序编写分工、程 序的可维护性以及测试都有重要的关系,如:从设计角度来看,在“强内聚、松耦合”的指导下进行的设计得到的程序模块,符合项目管理的WBS(工作分解结 构)的要求,其相对独立的模块可以分配到具体的程序员进行开发,另外,程序编码外包也必须建立在这种原则的设计之下;从程序生命期角度来看,它有利于提高 程序质量,特别是方便于程序的日后维护,即程序模块的相对独立性是可维护性的保证;再从测试角度来看,符合“强内聚、松耦合”的程序,易于对局部(模块) 进行黑盒测试,也易于编写测试用的“桩”和“驱动”。

 

“强内聚、松耦合”也是对组织结构的要求,项目组分为几个小组(正式的或非正式 的),各小组的工作应是高度相关的,各小组之间的工作应尽量是较少相关或有明确的接口,从而减少沟通成本。其实,“强内聚、松耦合”是系统中应遵守的普遍 原则,我们在许多领域都可以找到它的应用。

 

“强内聚、松耦合”是我们不得不念的“三字经”,我们一定要念好它。

高内聚和低耦合是同义词。

 

1。高内聚,指自成一体。

 

2。低耦合,指对外部的依赖很小。

 

只是他们侧重的角度不同,内聚侧重的是内政,而耦合侧重的是外交。

一个对象有两种外交形势,一是输出价值观,让别人调用,二是输入价值观,依赖别人。低耦合指的是输入价值观应该尽量少。如果必然要输入,那么输入的形式也有强弱之分,强耦合就是严重依赖别人。

 

从系统角度,如果要每个对象不依赖别人,却又妄想更多的输出价值观,这是矛盾的。因此,所谓的低耦合,指的是尽量减少不必要的输入,尤其避免强耦合。

 

从系统角度,越接近系统底层的,越具体的,耦合越强,而越高层的,越抽象的,耦合越低。

 

上章回顾

       在上篇中我们讲解了几类UML2.0语言新推出的建模图形,总体来说通过这些图形能更详细的将某类信息表达出来。在这里我们简单回顾上篇讲解的内容。

上图中已经简单介绍了上章讲述的内容,具体内容请看:系统架构师-基础到企业应用架构-系统建模[下篇]

 

二、摘要

       本章将主要的简单介绍在系统架构中的设计模式及相应规范准则。并结合相应的代码来说明如何遵循系统架构中的一些基本的设计规范及准则。而我们将在本文介

 

绍几类常用的设计规范,我们先来看看结构化设计的二个基本原则:

 当然既然提出了基本的准则,那么我们如何来满足准则呢,并且能更好的设计呢?我们可以通过如下手段来达到这样的要求:

 

        当然图中演示了功能分离的策略,通过把需求按照不同的功能详细的划分开来,每个功能都是独立

 

的,当然我们这里也可以成为是关注点。下面我们将针对这些原则进行详细的讲解。

 

三、本章内容

       1、上章回顾。

 

       2、摘要。

 

       3、本章内容。

 

       4、设计规范及原则。

 

       5、如何满足设计要求。

 

       6、本章总结。

 

       7、系列进度。

 

       8、下篇预告。

 

四、设计规范及准则。

      1、高内聚

        首先我们来看看内聚的含义:软件含义上的内聚其实是从化学中的分子的内聚演变过来的,化学中的分子间的作用力,作用力强则表现为内聚程度高。在软件中内

 

聚程度的高低,标识着软件设计的好坏。

 

        我们在进行架构设计时的内聚高低是指,设计某个模块或者关注点时,模块或关注点内部的一系列相关功能的相关程度的高低。

 

        例如:下单模块:

 

         一般情况下,下单模块都会有如下的信息,订单的信息,产品的信息及谁下的单(买家信息)。这是基

 

本的,那么我们设计的时候就要把相关的功能内聚到一起。当然这是从大功能(下单管理)上来说,当然这些模块还可以再细化分成产品、订单、会员等子模块。

 

        例如我们在设计数据库操作辅助类提供的方法有:

 

         通过这样的方式,那么这个组件只负责数据库操作。这样带来的好处也是显而易见的。高内

 

聚提供了更好的可维护性和可复用性。而低内聚的模块则表名模块直接的依赖程度高,那么一旦修改了该模块依赖的对象则无法使用该模块,必须也进行相应的修改才

 

可以继续使用。

 

        低内聚的模块设计的坏处有:首先模块的功能不单一,模块的职责不明确,比较松散,更有甚者是完成不相关的功能。这样的设计往往是不可取的。可以通过重

 

构来完善。

 

        下面我们来说下高内聚的简单解释:什么样的模块算是高内聚,并且能够在系统中很好的使用。

 

        

 

        那么我们在设计的过程中如何去完成高内聚呢?

 

        

 

        以上基本上讲述了高内聚的好处,并且阐述了如何实现高内聚的步骤和原则。下面我们来说说可能高内聚带来的坏处。

 

        高内聚有时候也不是说所有的情况都采用这样的原则,当然高内聚还是要适度的,下面来举例说明:例如内聚性要求强的话就像Windows32中系统提供的

 

API,里面的函数太多了,都放在一个Dll中,那么每个函数完成一个功能。这样强大的功能,会比较复杂,所以并不是完全的高内聚越高越好,还是要看实际的需要。

 

当然维护起来也不是特别的方便。

 

      2、低耦合

         首先我们来看看低耦合的定义:低耦合是用来度量模块与模块直接的依赖关系。耦合当然也可以这样简单的理解,我想懂电脑的应该都知道,CPU与主板之间的

 

关系,CPU如果是特殊的CPU必须使用特殊的主板来支持,那么如果说这个CPU不唯一依赖唯一主板,那么就认为这个CPU与主板的关系是低耦合的关系。

 

         下面我们来举例说明低耦合的设计与高耦合的设计:

 

           这是一个简单的低耦合的设计,电器与插座之间是低耦合的关系,就算我替换了不同的插座,电器依

 

然可以正常的工作。因此简单的描述如下,就是A模块与B模块存在依赖关系,那么当B发生改变时,A模块仍然可以正常工作,那么就认为AB是低耦合的。

 

         

 

         1、笔记本接音响可以正常的使用。

 

         2、笔记本接专配耳机正常的使用。       

 

         对应一般的音响来说,笔记本是通用的,音响和笔记本直接的关系是低耦合的,但是笔记本和耳机却是高耦合的,只有专配的耳机才能和笔记本互联使用,而不

 

是通用的,所以说笔记本和专配耳机存在着较强的依赖关系。当然最简单的方式就是笔记本提供统一的耳机接口,可以满足一般性的需求。

 

         下面我们将来分析如何构建低耦合的设计。

 

         

 

        总结

          上面我们已经讲解了低耦合和高内聚的二个原则,通过这2个原则我们知道,满足这2个原则是衡量一个架构设计好坏的一个参考标准。下面我们将来讲解通过

 

功能分离的方式来满足上面的2个原则。

 

五、如何满足设计要求

       1、如何按功能进行模块化的分离。

 

       我们在将一个系统进行功能划分时,我们一般如下来进行:

 

       首先、我们先把功能职责划分成独立的单元。

 

       例如现在有个B2C系统,那么我们按照B2C的需求,如下分析:

 

        我们这里简单的分析下B2C应该具有的功能模块,当然这些模块的划分中,有些模

 

块还可以继续的分离,当然我这里只是实例分析出来。

 

      2、对分离出来的模块化进行抽象,例如我们以支付为例。

 

           这里通过支付接口向外提供服务。那么外界模块不关心支付系统模块的变化,只需要调用接口

 

即可,如果具体的支付方式,比如支付宝的方式发生改变,在调用支付服务的模块中也不需要做任何的修改就可以正常的提供服务。显然这样的方式是不错的实现方

 

式。

 

       通常情况下我们在系统分离式只是以接口的方式提供服务,供其他的模块进行使用。在模块内部有大量的信息是不要向外部暴露的,所以模块在设计时访问域的定

 

义就要划分好,防止因为访问域的定义而对模块的信息造成破坏。

 

       下面我们来看下功能分离在不同的设计理念下都是什么样的表现:

 

       

 

       上面只是实体性的分析了功能分离的好处及应用的广度,当然我们在后续会结合实例来讲解如何来实现这样的软件设计模式。当然这只是软件的架构设计,那么如

 

果细化到具体的实现呢?我们如何去设计每个功能点呢?这就是下章我们要讲解的内容了,那么本文先列出二种常见的方式。

 

         

0 0
原创粉丝点击