《OOD启思录》—第2章2.3节 类耦合与内聚

2.3 类耦合与内聚
OOD启思录
一些经验原则用于解决类的耦合与内聚问题。我们努力让类更紧密地内聚,并尽量降低类间耦合程度。这和在面向动作范型中试图让函数更紧密地内聚并尽量降低函数间的耦合程度的努力是一致的。函数中的紧密内聚意味着组成函数的所有代码都是紧密相关的。函数间的松耦合意味着当一个函数想要使用另一个函数时,它应当在总是从同一点进入该函数,并从同一点退出。这样,我们就可以得出这样的面向动作的经验原则:“函数应当只有一条返回语句。”

在面向对象范型中,我们把松耦合和紧内聚的目标映射到了类的层次。类之间有5种形式的耦合关系:零耦合(nil coupling)是最佳的,因为这意味着两个类丝毫不依赖于对方。你可以去掉一个类,而不会影响另一个。当然,如果只用到零耦合,你无法创建有意义的应用程序。若只用到零耦合,我们最多只能创建类库,这样的类库由一系列的独立类组成,这些类相互之间没有影响。导出耦合(export coupling)则表明,一个类依赖于另一个类的公有接口。1也就是说,这个类用到另一个类的一个或多个公有操作。授权耦合(overt coupling)则意味着一个类经允许使用另一个类的实现细节。C++的友元机制是授权耦合的典型例子。一个C++类X可以声明类Y是它的友元。这样,Y的方法就获得授权可以访问X的实现细节。自行耦合(covert coupling)和授权耦合差不多,也是类Y访问类X的实现细节,但区别在于类Y是未经授权的。如果我们发明一种语言机制,允许类Y声明自身是X的友元并且将使用X的实现细节,那么X和Y就是自行耦合的。最后一种耦合是暗中耦合(surreptitious coupling),这种耦合是指类X通过某种方式知道了Y的实现细节。如果类X使用类Y的公有数据成员,那么X就和Y暗中耦合。暗中耦合是最危险的耦合形式,因为它在Y的行为和X的实现之间建立了很强的隐式依赖关系。

经验原则2.7
类之间应该零耦合,或者只有导出耦合关系。也即,一个类要么同另一个类毫无关系,要么只使用另一个类的公有接口中的操作。

所有其他形式的耦合都允许类把实现细节暴露给其他类,这样就在两个类的实现之间建立了隐含依赖关系。将来如果一个类想要修改它的实现,那么这些隐含依赖关系总会带来维护问题。

类内聚努力确保类内部的所有元素都是紧密关联的。有一些经验原则牵涉到这一属性。

经验原则2.8
类应当只表示一个关键抽象。

一个关键抽象(key abstraction)被定义成领域模型中的一个主要实体。关键抽象经常以名词形式出现,并伴随着需求规约。每个关键抽象都应当只映射到一个类。如果它被映射到多个类,那么设计者可能是把每个功能都表示为一个类了。如果多个关键抽象被映射到了同一个类,那么设计者可能在创建一个集中化的系统。这些类经常被称为含糊的类(vague classes),并且需要分割成两个或多个类,每个类表示一个关键抽象。第3章我们将更详尽地探讨这两种不良设计。

经验原则2.9
把相关的数据和行为集中放置。

如果违反这条经验原则,那么开发者就不得不按以往方式编程。为了实现单一的系统需求,开发者不得不改动系统的两处或者多处。其实这两处(或者多处)是同一个关键抽象,所以应当用同一个类表示。设计者应当留意那些通过get之类操作从别的对象中获取数据的对象。这种类型的行为暗示着这条经验原则被违反了。考虑一下一个烤炉类的使用者想要在烧烤之前预热烤炉。用户应当只需要发送给烤炉一条are_you_preheated?()消息就可以了。烤炉应当可以测试自己的温度是否已经达到了需要的温度,并且测试其他预热需要满足的条件。如果用户为了知道烤炉是否已经预热,需要问烤炉目前温度、期待温度、燃气阀的状态、常燃火状态等等,那么就违反了这条经验原则。烤炉拥有这些温度和燃气烹饪设备的信息,它应当自行判断这个对象是否已经预热了。留意那些为了实现不正确的预热方式而需要用到的get方法(比如,get_actualtemp()、get_desiredtemp()、get_valvestatus()等等)是很重要的。

经验原则2.10
把不相关的信息放在另一个类中(也即:互不沟通的行为)。

开发者应当留意这样的类:方法的一个子集操作数据成员的一个真子集2。极端情况是,一个类有一半方法操作一半数据成员,另一半方法则操作另一半数据成员(见图2.6)。

这是一个更接近现实世界的例子。请考虑词典类。对于小型词典,最好的实现是属性列表(单词和它们定义的列表),但是对大型词典来说,哈希表更好(更快)。两种辞典的实现都需要提供增加单词和寻找单词的能力。图2.7展示了一个具有互不沟通的行为的词典类设计。

这个解决方案假设词典类的使用者知道词典将会有多大。他们需要做出决定,是使用哈希表实现的词典还是链表实现的词典。一般而言,在类名中显示实现细节并让用户来做这样的选择不是好主意。一个更好的解决方案留在第5章讲述,因为它要用到继承。在那个解决方案中,一个单一的词典类把它的实现隐藏为内部细节。如果词典的大小增长到了一个事先定下的临界值,词典类会决定改变实现。

1译注:在Eiffel中可以显式地声明导出关系。
2译注:子集(subset)和真子集(proper subset)的区别在于,一个集合是其本身的子集,但不是其本身的真子集。
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。

时间: 2024-10-12 03:11:53

《OOD启思录》—第2章2.3节 类耦合与内聚的相关文章

《OOD启思录》—第2章2.5节 抽象类

2.5 抽象类OOD启思录除了我们已经讨论过的类,还有一种重要的抽象类型是我们需要探讨的.请思考下列问题:你曾经吃过水果吗?你曾经吃过开胃菜吗?你曾经吃过甜点吗?很多人对这3个问题的答案都是"是".只要你对这3个问题中的任一个回答了"是",请你接着思考下面的问题:水果尝起来味道如何?一份甜点有多少卡路里的热量?一份开胃菜价格是多少? 我可以说,没有人吃过"水果".很多人吃过苹果.香蕉或者桔子,但没有人吃过一个3斤重的.红色的就叫做"水果

《OOD启思录》—第2章2.1节类和对象导引

第2章 类和对象:面向对象范型的建材OOD启思录2.1 类和对象导引面向对象范型使用类和对象的概念作为基本建筑材料.应用程序的分析.设计.实现模型一致地使用这些概念.通过现实世界中的例子来解释这些概念是最佳方案.如果有一屋子的人,你问:"给你们所需的全部零件,谁能装配出一只闹钟"?最多有一两个人会举手.但如果你问他们"这个房间里谁能够把闹铃设到早上9点",那么我可以放心地和你打赌,大多数人都会举手.大多数人会使用闹钟,但不会装配闹钟,这难道不荒谬吗?对这个问题,你最

《OOD启思录》—第1章1.1节革命家、改革家与面向对象范型

第1章 面向对象编程的动因OOD启思录1.1 革命家.改革家与面向对象范型在学习面向对象范型以及相关知识的过程中,你首先必须知道我们社区中的很多对立观点.每组对立观点意味着两个或者多个阵营,他们对自己的观点一般都具有宗教般的热情.最重要的对立观点之一是革命家与改革家之争.革命家相信,有一群开发者某一天在凌晨3点醒来,并发现以前我们一直都在用错误的方式开发软件.他们相信,他们找到了解决软件危机的方法,并且把这种方法叫做"面向对象编程".或许读者已经猜到,我是属于改革家阵营的. 改革家认为

《OOD启思录》—第2章2.4节动态语义

2.4 动态语义OOD启思录除了固定的数据和行为的描述之外,对象在运行时还随着其数据描述的动态取值具有局部状态(即当时的"快照").类的对象的所有可能状态的集合以及状态间合法的变换称为类的动态语义(dynamic semantics).动态语义允许对象对其生命期的两个不同时候发来的相同消息作出不同的回应.例如,看这个抽象例子: Method junk for the class X if (local state #1) then do something else if (local

《OOD启思录》—第1章1.2节Frederick Brooks观点:非根本复杂性与根本复杂性

1.2 Frederick Brooks观点:非根本复杂性与根本复杂性OOD启思录Frederick Brooks在1987年10月份的IEEE Computer上发表了一篇有趣的文章,标题是Conceptual Essence of Software Engineering or There Is No Silver Bullet [参考文献1].Frederick Brooks是The Mythical Man-Month [参考文献2]的作者.The Mythical Man-Month记

《OOD启思录》—第2章2.2节消息和方法

2.2 消息和方法OOD启思录对象应当被看作机器,机器只为提出恰当请求的人执行公有接口所定义的操作.因为对象独立于使用者,也因为一些实现了面向对象概念的早期语言的语法,术语"发送消息"用于描述执行对象的行为.当消息被发送至对象,它必须判断是否理解该消息.如果理解,那么对象就把消息映射为一个函数调用,并把自身作为隐含的第一个参数传递过去.对解释语言而言,判断是否理解一个消息是在运行时完成的,而编译语言则是在编译时完成的. 对象行为的名称(或者原型)被称作消息(message).许多面向对

《OOD启思录》—第1章1.4节迭代模型

1.4 迭代模型OOD启思录软件开发的迭代模型看上去和瀑布模型差不多,区别只在于迭代模型允许开发者沿项目流程往返(见图1.2).如果我们在为系统的某个部分编写代码时发现了一个设计缺陷,我们可以回到设计阶段来分析并改正它.或者,如果我们在测试系统的一部分时发现了新的系统需求,我们可以回到分析阶段来修正这个问题.在面向动作范型中,迭代模型会带来很多问题.面向动作的软件常常会有很多位于数据和行为之间的隐含依赖关系.再同集中控制机制一结合,你就会发现自己处于这样一个境地:如果触动了已经存在的应用程序的部

《OOD启思录》—第2章2.6节角色与类

2.6 角色与类OOD启思录经验原则2.11确保你为之建模的抽象概念是类,而不只是对象扮演的角色. "母亲"或者"父亲"是不是类,还是某个"人"对象所扮演的角色?答案取决于设计者为之建模的领域是什么.如果在给定的领域中,母亲和父亲具有不同的行为,那么或许他们应当被建模为类.如果他们的行为相同,那么他们只是"人"类的对象所扮演的不同角色.例如,我们可以把家庭看作"父亲"类的对象."母亲"类

《OOD启思录》—第1章1.7节优秀设计者阶层

1.7 优秀设计者阶层OOD启思录Brooks在"No Silver Bullet"一文中提到的作为控制根本复杂性方法的最后一个话题是在企业中建立一个优秀软件设计者阶层,让他们从大批初级设计者中选拔接班人.这可以同管理者阶层类比:高级经理位居顶端,并且从大批初级经理中选拔接班人.这触及了软件开发者间"艺术还是科学"这一争论的核心.软件开发能力是后天习得的,还是需要天赋?我不想参加这一争论,但我想做个类比.如果有人拿枪指着我的脑袋逼我在一年内学会弹钢琴(我之前从未学过