亚里士多德可能是第一个仔细研究类型这一概念的人,他曾经提出过“鱼的类别和鸟的类别”。所有的对象,哪怕是相当独特的对象,都能够被归为某一类,并且同一类对象拥有一些共同的行为和特征。作为有史以来第一门面向对象编程语言,Simula-67 引入了上述的“类别”概念,并且允许通过关键字 class 在程序中创建新的类型。

    Simula 语言恰如其名,其诞生的目的是用于“模拟”,比如模拟经典的“银行出纳问题”。这个问题的元素包括大量的出纳员、顾客、账户、交易,以及各种货币单位等,这些都是“对象”。而那些状态不同但结构相同的对象汇聚在一起,就变成了“同一类对象”(classes of objects),这就是关键字 class 的由来。

    创建抽象数据类型(即“类”)是面向对象编程的一个基本概念。抽象数据类型的工作原理和内置类型几乎一样:你可以创建某种类型的变量(在面向对象领域,这些变量叫作“对象”或“实例”),随后你就可以操作这些变量(叫作“发送消息”或“发送请求”,即你发送指令给对象,然后对象自行决定怎么处理)。同一类型的所有成员(或元素)都具有一些共性,比如:每一个账户都有余额,每一位出纳员都能处理存款业务。同时,每一个成员都具有自己的专属状态,比如:每一个账户的余额都是不同的,每一位出纳员都有名字。因此,对于所有这些成员,包括每一位出纳员、每一位顾客、每一个账户,以及每一笔交易等,我们都能够在程序中用一个唯一的实体来表示。这种实体就是对象,同时每一个对象所归属的类决定了对象具有何种行为特征。

    虽然我们在面向对象编程中会创建新的数据类型,但实际上所有面向对象编程语言都会使用 class 这个关键字。所以当你看到“类型”(type)这个词的时候,请第一时间想到“类”(class),反之亦然。2

    2有时候我们会将两者加以区分,将类型(type)定义为接口,而类(class)则是接口的具体实现。

    因为类描述了一系列具有相同特征(即数据元素)和行为(即功能方法)的对象,而即便是浮点数这种内置数据类型也具有一系列的行为和特征,所以类其实就是数据类型。抽象数据类型和内置数据类型的区别是,程序员可以通过定义一个新的类来解决问题,而非受限于已有的数据类型。这些已有的数据类型其设计本意是为了呈现机器内的存储单元,你可以根据实际的需求创建新的数据类型,同时扩展编程语言的能力。此外,编程系统对于新的类十分友好,比如也会为新的类提供类型检查等功能,就像对待内置数据类型一样。

    面向对象编程的作用并不局限于模拟。无论你是否同意“任何程序都是对系统的一种模拟”,面向对象编程技巧都可以帮你将众多复杂的问题简化。

    一旦创建了一个类,就可以用它创建任意多个对象,然后在操作这些对象时,可以把它们视为存在于问题空间的元素。实话实说,面向对象编程的一大挑战就是,如何在问题空间的元素和解决方案空间的对象之间建立一对一的关联。

    那么,如何能让一个对象真正发挥其作用呢?答案是向对象发送请求,比如让它完成一次交易、在屏幕上画个图形或者打开一个开关等。对象能够接受什么请求,是由它的“接口”(interface)决定的,而对象所归属的类定义了这些接口。接下来以电灯泡为例,如图1-1所示。
    image.png图1-1

    1. Light lt = new Light();
    2. lt.on();

    图1-1中的接口定义了你能够向这个对象发送的请求。此外,也必然存在一些代码用于响应这些请求。这些代码再加上隐藏的数据,叫作“实现”(implementation)。对于每一个请求,类都有一个方法与之对应。当你向一个对象发送特定的请求时,对应的方法就会被调用。我们通常会这样描述该过程:向对象“发送消息”(即发出请求),然后由对象决定如何处理(即运行对应的代码)。

    在上面的例子中,类的名字是 Light,Light 所生成的对象的名字是 lt,我们能够对 Light 对象发出的请求是开灯(on())、关灯(off())、灯光变亮(brighten())以及灯光变暗(dim())。通过定义一个“引用”即 lt,以及用 new 关键字生成一个新对象,我们就创建了一个 Light 对象。此外,如果你需要向对象发送消息,可以用一个英文句号(.)将对象名和请求(即方法)连接起来。如果我们只是使用内置类,那么基本上关于对象编程的内容就是以上了。

    此外,前面的图示遵循了统一建模语言(Unified Modeling Language, UML)的规范。在此规范下,每一个类都表示为一个方块,方块头部是类名,方块中部是你想要描述的数据成员,而方法(即该对象的函数,负责接收发送至对象的请求)则位居方块的底部。通常,UML 图中只会展示类名和公有方法,所以在上图的例子中,方块中部的内容并没有展示出来。如果你只关心类名,方块底部的内容也可以不显示。