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

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

对象行为的名称(或者原型)被称作消息(message)。许多面向对象语言都支持重载函数(overloaded function)或者操作符。这一构造的约定是,系统中的两个函数可以有相同的名字,只要它们的参数类型不同(类内重载)或者所属的类不同(类间重载)就可以了。闹钟类可以有两个不同的set_time消息,一个消息用两个整数作为参数,另一个消息用一个字符串作为参数。这是一个类内重载的例子。

void AlarmClock::set_time(int hours, int minutes);
void AlarmClock::set_time(String time);

此外,闹钟和手表可能都有set_time消息,它们可能都以两个整数作为参数。这是一个类间重载的例子。

void AlarmClock::set_time(int hours, int minutes);
    void Watch::set_time(int hours, int minutes);

值得一提的是,消息的组成部分包括函数名、参数类型、返回值类型,以及消息所属的类。这是类的使用者所需知道的主要信息。在一些语言和/或系统中,可能还会有其他信息,比如消息抛出的异常的类型,以及其他相关的同步信息(比如,消息是同步的还是异步的)。类的实现者必须知道如何实现消息。消息的实现,也即实现消息的代码,被称作方法(method)。一旦控制进入方法内部,对接收消息的对象的全部数据成员都是通过隐含的第一个参数引用的。这个隐含的第一个参数在很多语言中都称作“self对象”(C++则偏爱称其为“this对象”)。对象所能响应的消息列表被称作对象的协议(protocol)。

类/对象可以响应两种特殊的消息。第一种是用于为了创建类的对象而调用的操作。这称为类的构造函数(constructor)。类可以有多个构造函数,每个构造函数接受一组不同的初始化参数。例如,我们可以通过传递5个整数参数分别指明小时、分钟、闹铃小时、闹铃分钟、闹铃状态来构造闹钟;我们也可以传递两个字符串和一个整数参数,每个字符串都是“小时:分钟”格式,分别表明时间和闹铃时间;而整数则表明闹铃状态。有的类甚至可以有十几个或者更多构造函数。

类/对象能够响应的第二种特殊的消息是在把对象从系统删除之前清除对象内容的操作。这个操作称为类的析构函数(destructor)。大多数面向对象语言每个类都只有一个析构函数,因为在运行时需要做出的任何决定都可以保存为对象状态的一部分,没有必要再给方法传递额外的参数。我们将在书中多处提及构造函数和析构函数。你可以认为它们是面向对象范型的初始化和清除机制。

经验原则2.2
类的使用者必须依赖类的公有接口,但类不能依赖它的使用者。

这条经验原则背后的基本原理是可复用性。闹钟可以用于卧室(参见图2.4)。使用闹钟的人显然依赖于闹钟的公有界面。但是,闹钟不应当依赖于那个人。如果闹钟依赖于使用者,比如说那个在卧室中用闹钟的人,那么闹钟就无法被用来制造定时锁保险箱,除非把那个人也绑定在保险箱上。这样的依赖性是不受欢迎的,因为我们想要把闹钟用于其他的领域,而不想为此依赖于使用者。所以,最好把闹钟看作一个小型机器,这个小型机器对它的使用者一无所知,它仅仅是执行定义于公有界面的行为,而不管发送消息的是谁。

经验原则2.3
尽量减少类的协议中的消息。
就在几年前,还有人撰文提倡刚好与这条经验原则相反的实践。当时是这样说的:关于这个类的操作,凡是类的实现者能想象到的,将来就会有用户用到。那么,既然如此,为什么不实现这些操作呢?如果你采纳这样的经验原则,那么你肯定会钟爱我的链表类——它的公有接口有4 000个操作。问题时,当你想对两个链表对象执行合并操作时,你认为链表类一定提供了这个操作,所以你依照字母顺序检查消息列表,但是却找不到哪个操作是以merge、union、combine或者你知道的其他同义词命名的。不幸的是,真正的操作是一个重载的加号(在C++中是operator+)。庞大的公有接口的问题是,你永远都无法找到你想要找的东西。这严重损害了接口的可复用性。而如果让接口最小化,我们就可以让系统易于理解,并使组件易于复用。

经验原则2.4
实现所有类都理解的最基本公有接口[例如,拷贝操作(深拷贝与浅拷贝)、相等性判断、正确输出内容、从ASCII描述解析等等]。

如果一个开发者设计和实现的类要被另一个开发者在其他应用程序中复用,那么提供一个常用的最小公有接口常常很有用。1这个最小公有接口包含的功能是人们合理地预期每个类都会有的。我们可以把这个接口当作了解可复用软件代码中类的行为的基础。我们将在第9章中更详细地探讨关于这个最小公有接口的事项。

经验原则2.5
不要把实现细节(例如放置共用代码的私有函数)放到类的公有接口中。

这条经验原则用于为使用者降低类接口的复杂性。基本想法是,类的使用者不想在公有接口中看见他们不用的成员。这些成员属于类的私有区域。如果类的两个方法有一段公共代码,那么就可以创建一个防止这些公共代码的私有函数。把这些公共代码封装成一个独立方法常常会带来方便,但是这个方法并不是一个新的操作,它只是类中两个操作的实现细节。因为是实现细节,所以它应当放在类的私有区域中,而不是公共区域中(参见图2.5)。

为了让你对公共代码私有函数有更贴近实际的了解,你可以认为类X是一个链表,f1和f2是函数insert和remove,公共代码私有函数f是在链表中找到插入点或者删除点位置的操作。
经验原则2.6
不要以用户无法使用或不感兴趣的东西扰乱类的公有接口。

这条经验原则与前一条是相关的,因为类的用户不会想调用公共代码函数,所以把这些函数放在公有接口中只会扰乱类的公有接口。它们并不是类的新操作。有些语言,比如C++,允许在公有接口中错误地包含其他类型的函数。例如,在C++中把抽象类的构造函数放在那个类的公有接口中是合法的,虽然当类的使用者试图使用这样的构造函数时编译器会报告一条语法错误。若遵循更一般化的经验原则2.6,那么这些问题就不会发生了。

1译注:特别是Framework设计尤其如此。很多Framework设计时都在根类中提供了这一最小公有接口(单根继承结构)。
本文仅用于学习和交流目的,不代表异步社区观点。非商业转载请注明作译者、出处,并保留本文的原始链接。

时间: 2024-08-04 14:33:50

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

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

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

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

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

《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.3节 类耦合与内聚

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

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