1.2 对象有一个接口
亚里士多德可能是第一个认真研究类型(type)概念的人,他提到了“鱼类和鸟类”。所有对象(虽然都具有惟一性)都是一类对象中的一员,它们有共同的特征和行为。这一思想在第一个面向对象语言Simula-67中得到了直接的应用,该语言用基本关键字class在程序中引入新类型。
顾名思义,创造Simula的目的是为了解决模拟问题,例如著名的“银行出纳员问题[1]”。其中包括出纳员、顾客、账户、交易、货币的单位等大量的“对象”。把那些在程序执行期间的状态之外其他方面都一样的对象归为“几类对象”,这就是关键字class(类)的来源。创建抽象数据类型是面向对象程序设计的基本思想。抽象数据类型几乎能完全像内建类型一样工作。程序员可以创建类型的变量[面向对象的说法称为对象(object)或实例(instance)]和操纵这些变量(称为发送消息或请求,对象根据发来的消息推断需要做什么事情)。每个类的成员(元素)都有共性:每个账户有余额,每个出纳员都能接收存款,等等。同时,每个成员都有自己的状态,每个账户有不同的余额,每个出纳员都有名字。这样,在计算机程序中,出纳员、客户、账户、交易等,每一个都被描述为惟一的实体。这个实体就是对象,每个对象都属于一个定义了它的特性和行为的特定类。
所以,虽然在面向对象的程序设计中,我们所做的工作实际上是创造新数据类型,但事实上所有的面向对象的程序设计语言都使用关键字“class”。当碰到“类型(type)”时可以看做“类(class)”,反之亦然。[2]
由于类描述了一组有相同特性(数据元素)和相同行为(功能)的对象,因此类实际上就是数据类型,例如浮点数也有一组特性和行为。区别在于,程序员定义类是为了与具体问题相适应,而不是被迫使用已存在的数据类型,而设计这些已存在的数据类型的动机是为了表示机器中的存储单元。程序员可以通过增添专门针对自己需要的新数据类型来扩展程序设计语言。这种程序设计系统欢迎新的类,关注新的类,对它们进行与内置类型一样的类型检查。
面向对象方法并不限于模拟创建。无论我们是否同意,都不能否认任何程序都是我们正在设计的系统的一种模拟,OOP技术的使用确实可以容易地将大量问题缩减为一个简单的解决方案。
一旦建立了一个类,程序员想制造这个类的多少个对象就可以制造多少个,然后操作这些对象,就如同它们是所解决的问题中的元素。实际上,面向对象程序设计的难题之一,是在问题空间中的元素和解空间的对象之间建立一对一的映射。
但是,我们如何得到一个对象去为我们做有用工作呢?必须有一种方法能向对象作出请求,使得它能做某件事情,例如完成交易、在屏幕上画图或打开开关。每个对象只能满足特定的请求。可以向对象发出的请求是由它的接口(interface)定义的,而接口由类型确定。一个简单的例子可能该是电灯泡的表示了。
接口规定我们能向特定的对象发出什么请求。然而,必须有代码满足这种请求,再加上隐藏的数据,就组成了实现(implementation)。从过程型程序设计的观点看,这并不复杂。类型对每个可能的请求都有一个相关的函数,当向对象发请求时,就调用这个函数。这个过程通常概括为向对象“发送消息”(提出请求),对象根据这个消息确定做什么(执行代码)。
如上例,这个类型或称类的名字是Light,这个特定的Light对象的名字是lt,可以对Light对象提出一些请求:打开它、关闭它、使它变亮或变暗。通过声明一个名字(lt),可以创建一个Light对象。为了向这个对象发送消息,可以说出这个对象的名字,并用句点连接对消息的请求。从使用预定义类的用户的角度看,用对象编程只需要这些工作就行了。
上图符合统一建模语言(Unified Modeling Language, UML)的格式,每个类由一个方框表示,这个方框的顶部标有类型名,中间部分列出所关注的数据成员,底部是成员函数(member function属于这个对象的函数,它们能接收发送给这个对象的任何消息)。通常,只有类的名字和公共成员函数会在UML设计图中表示出来,所以中间部分不显示。如果只对类的名字感兴趣,则底部也不需要显示。
[1]可以在本书的第2卷中找到这一问题的有趣实现,本书的第2卷可从www.BruceEckel.com上找到。
[2]一些人对此做了区分,他们认为类型(type)确定接口,而类(class)是对这个接口的特定实现。