在面向对象的设计中,我们经常会听到或用到聚合、耦合的概念。面向对象的目标就是设计出高聚合、低耦合的程序。然而,究竟什么是聚合、什么是耦合,恐怕每个人都有自己的答案,换句话说,大多数人对聚合和耦合的概念是模糊的。小弟我今天就在此抛砖引玉,希望能给新入行的朋友和在校的学生一点帮助。
因为聚合与耦合这两个概念一直都是以"高聚合、低耦合"的形式出现的,刚刚开始接触面向对象设计时,我一直认为聚合和耦合是一对相反的概念,也就是说:只要做到了高聚合,那么自然而然就做到了低耦合。虽然这样的理解并不是错误的,但我并没有思考过原因。
先来看看聚合的定义:聚合(Cohesion)是一个模块内部各成分之间相关联程度的度量。
这里有多个含义值得考虑。首先,聚合是对一个模块内部的度量,这也是许多情况下我们把聚合称之为内聚的原因。第二,这里出现的模块是广义的模块,它可能是子系统,可能是功能模块,也可能是功能模块中的某一个类。从不同的层次看,聚合的程度也会有所不同。至于为什么不同,后面会有解释。第三,模块的成分包括模块的行为和状态。要做到高聚合,那么模块内部的行为必须要与模块的内部状态紧密关联。通俗来讲,一个模块仅完成一个独立的功能,模块内部不存在与该功能无关的操作或状态。
举一个生活中的例子。
有两座城市Sidtown和Fredborough,连接两座城市的公路一天到晚总是拥堵不堪。经过"有关部门"调查之后发现,这两座城市中有两家公司Better Mousetrap和 Zokko Soda,Better Mousetrap的工厂建造在Sidtown,而该工厂的员工都居住在Fredborough,所以每天早上大批员工从Fredborough出发前往Sidtown,并在傍晚返回;类似的,Zokko Soda公司的运输车在每天的工作时间都需要在制瓶工厂和灌装工厂穿梭来往。
很明显,如果Better Mousetrap的工厂和员工居住地都在同一城市,而Zokko Soda的两座工厂都建造在另一座城市,那么城市之间的交通状况将会明显改善。
对比两图,上面两座城市间之所以出现交通的问题,是因为每座城市的"聚合性"都比较低:不相关的两个公司出现在了同一座城市,使得城市内部交通的利用率比较低,而城市之间的交通出现了超负荷。
再来看看耦合的定义:耦合(Couping)是模块之间相关联程度的度量。相对于聚合的内向性,耦合关注的是某一模块和其他模块之间的关联性。其实从前面的例子里,我们已经不可避免的提到了耦合的问题:由于两座城市之间的相互联系过于紧密,导致了城市之间的交通拥堵。另外一个潜在的问题就是,如果其中一座城市内部的交通出现了问题,另一座城市也会受到影响。我们所追求的低耦合,就是将两个模块之间的关联尽可能的降低,一个模块发生变化对于其他模块的影响尽可能的小。
再讲一个生活中的例子,相信大部分的80后小的时候都玩过一种掌上游戏机,这种游戏机内含一个俄罗斯方块的游戏。这种游戏机虽然风靡一时,但是不多久就渐渐淡出了市场,因为这种游戏机只有俄罗斯方块可以玩儿,当我们玩儿腻了的时候,这个游戏机也就如同废物一个了。
同期,任天堂推出一款后来风靡了将近20年的红白机,这种游戏机市场寿命如此之长并非游戏机本身质量有多好,而是因为基于红白机开发的游戏层出不穷,经典无数。魂斗罗、超级玛丽在当时哪怕是现在也是无人不知。红白机的游戏本身并不存储在游戏机当中,每当有新游戏推出的时候,只需要购买新的卡带即可。正是这种游戏机和卡带相对独立的设计,使得游戏的设计厂商无需关心游戏机的实现细节,只要遵循游戏机提供的接口(插槽)。很多游戏的设计厂商也从红白机庞大的市场中分得一杯羹。大多数的玩家可能不知道,魂斗罗并非任天堂推出的产品,而是目前以《实况足球》系列闻名世界的KONAMI公司于1988年从街机移植到红白机上的。
回到耦合的话题上来,因为早先的掌上游戏机将游戏本身内置在机器当中,游戏和机器这两个模块之间的关系过于紧密,所以游戏玩儿腻了,游戏机就没用了,游戏机出问题了,游戏也再也不能玩儿了。而红白机的游戏和游戏机之间的关系是相对独立的,只要它们都遵循制定好的协议,就可以独立的发展和变化。游戏卡带摔坏了,其他的游戏一样可以在机器上运行;自己的游戏机坏了,把卡带拿到朋友家的游戏机上也能玩儿。红白机发展到后期,连游戏机的手柄也是可插拔的,如果手柄坏了,也只需要更换手柄即可。
讲到这里,大家对聚合和耦合应该也有了初步的认识。那么,我们如何看待聚合和耦合在实际当中的应用呢?我们的程序怎样才算是做到了高聚合和低耦合呢?
前面曾经提到,从不同的层次看,聚合和耦合的程度也会有所不同。Sidtown和Fredborough的例子当中,从城市的层次来看,第二种设计完全达到了高内聚和低耦合的目标,然而,如果从城市的不同区域来看,这样的设计内聚性还不够。如果我们一直追究下去,恐怕Better Mousetrap所有的员工都要住在生产线上了。一味的追求高内聚,必然会造成模块的功能过于单一,而模块的数量出现急剧膨胀。所以,我们在设计和实现程序时必须要斟酌模块间的聚合和耦合程度,有兴趣的朋友也可以去研究聚合性指标与耦合性指标。
软件设计的原则笔记:
1.让客户端做的尽可能少,服务器端做的尽可能多,减少两者的耦合性。
2.客户代码指定应该做什么,而服务器代码指定如何做。
3.客户端属于高层操作,服务器端负责低层操作。
4.客户端很低级,它们往往知道的“很少”,不要渴望它们知道的更多,因此,信息隐藏和数据封装都放到服务器端。
面向对象设计的原则是:高内聚,低耦合;
耦合:模块与其它模块之间的关系
低耦合是指:一个模块发生变化对于其他模块的影响尽可能的小。