购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.1 抽象的历程

所有编程语言都是一种抽象。甚至可以说,我们能够解决的问题的复杂程度直接取决于抽象的类型和质量。这里提到的“类型”的含义是“你要抽象的是什么”。比如,汇编语言是对计算机底层的一个极简化的抽象。还有许多所谓的命令式编程语言(比如FORTRAN、BASIC和C语言等)都是各自对汇编语言的抽象。虽然这些语言已经取得了长足的进步,但它们主要的抽象方式依然要求你根据计算机的结构而非问题的结构来思考。于是,程序员必须在机器模型(也叫作“解决方案空间”,即实际解决问题的方式,比如计算机)和实际解决的问题模型(也叫作“问题空间”,即问题实际存在之处,比如来源于某个业务)之间建立关联。建立这种关联需要耗费很大的精力,而且它是与编程语言无关的,这一切都导致程序难以编写且不易维护。

构建机器模型的一种代替方案是针对需要解决的问题构建问题模型。早期的一些编程语言(比如LISP和APL)会采取特定的视角看待周遭问题(例如,“所有问题最终都可以用列表呈现”或者“所有问题都是算法问题”),Prolog语言则会将所有问题都转换为决策链。这些语言要么是基于约束性的编程语言,要么是专门用来操作图形符号的编程语言。这些编程语言都能够出色地解决一些特定的问题,因为它们正是为此而生的。然而,一旦遇到它们专属领域以外的问题,它们就显得无能为力了。

面向对象编程则更进一步,它为程序员提供了一些能够呈现问题空间元素的工具。这种呈现方式具备足够的通用性,使得程序员不再局限于特定的问题。而这些问题空间中的元素及其解决方案空间中的具体呈现,我们称其为“对象”(需要注意的是,有些对象并不支持问题空间的类比)。其背后的理念则是,通过添加各种新的对象,程序可以将自己改编为一种描述问题的语言。于是,你阅读的既是解决方案的代码,也是表述问题的文字。这种灵活且强大的语言抽象能力是前所未有的。因此,面向对象编程描述问题的依据是实际的问题,而非用于执行解决方案的计算机。不过,它们之间依然存在联系,这是因为从某种意义上来说,对象也类似于一台小型计算机——每一个对象都具有状态,并且可以执行一些特定的操作。这一特点与现实中的事物极为相似,它们都具有各自的行为和特征。

SmallTalk是历史上第一门获得成功的面向对象语言,并且为后续出现的Java语言提供了灵感。Alan Kay总结了SmallTalk语言的5个基本特征,这些特征代表了纯粹的面向对象编程的方式。

1.万物皆对象。 你可以把对象想象为一种神奇的变量,它可以存储数据,同时你可以“发出请求”,让它执行一些操作。对于你想要解决的问题中的任何元素,你都可以在程序中用对象来呈现(比如狗、建筑、服务等)。

2.一段程序实际上就是多个对象通过发送消息来通知彼此要干什么。 当你向一个对象“发送消息”时,实际情况是你发送了一个请求去调用该对象的某个方法。

3.从内存角度而言,每一个对象都是由其他更为基础的对象组成的。 换句话说,通过将现有的几个对象打包在一起,你就创建了一种新的对象。这种做法展现了对象的简单性,同时隐藏了程序的复杂性。

4.每一个对象都有类型。 具体而言,每一个对象都是通过某个 生成的 实例 ,这里说的“类”就(几乎)等同于“类型”。一个类最为显著的特性是“你可以发送什么消息给它”。

5.同一类型的对象可以接收相同的消息。 稍后你就会意识到这句话的丰富含义。举例来说,因为一个“圆形”对象同样也是一个“形状”对象,所以“圆形”也可以接收“形状”类型的消息。这就意味着,你为“形状”对象编写的代码自然可以适用于任何的“形状”子类对象。这种可替换性是面向对象编程的一个基石。

Grady Booch对对象做了一种更为简洁的描述:

对象具有状态、行为及标识。

这意味着对象可以拥有属于自己的内部数据(赋予其状态)、方法(用于产生行为),同时每一个对象都有别于其他对象。也就是说,每一个对象在内存中都有唯一的地址。 iDJRllDQsoqExAim2bFxa+WuXhgT25BBDCRyiuhdRQFdT5LsIubWkjbiG5kvN6xm



1.2 对象具有接口

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

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

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

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

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

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

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

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

图1-1

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

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

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

点击中间区域
呼出菜单
上一章
目录
下一章
×