每个对象都有接口
亚里士多德也许是世界上第一个系统研究“类型”这一概念的人;他有这样的提法“鱼的类和鸟的类”。对于正在被区分的所有对象,同时也是拥有相同的特征和行为的类的对象中的一部分,这一思想直接应用于第一个面向对象语言Simula-67中,其中引入了新的基础关键字class来描述一个新的类型。
Simula,就像它的名字所暗示的一样,是用来开发诸如“出纳员问题”这样的经典问题的模拟环境的。在这一问题中,有几个出纳员,客户,账户,业务,和货币单位——这些全部可以看作“对象”。在程序执行期间,拥有相同的状态的对象将被同时分在一个“对象的类”组中,这就是class关键字的由来。创建抽象的数据类型(也就是类)是面向对象编程中最基本的概念之一。抽象数据类型与内置的数据类型的工作方式基本一致:你可以创建变量(案面向对象的说法,被称作对象或实例),操作对象(称为发送消息或者请求,你发送出一条消息,对象就知道该做些什么)。这些类的成员(元素)拥有一些共性:每个账户都有余额,每个出纳员都可以接受一次用户的存取操作,等等。类似地,出纳员,客户,账户,业务等等可以在计算机系统中以一个独一无二的实体的形势描述出来。这个实体就是对象,每个对象属于一个拥有其特定的特征和行为的类。
所以,尽管我们在面向对象编程中真正要做的是创建新的数据类型,实际上所有的面向对象编程语言都使用了“class”关键字。当你看到“类型”这个词时,你应该很自然的想起“class”,反之亦然。
由于类描述的是一系列的拥有同样的特征(数据元素)和行为(函数)的对象,一个类实际上是一个数据类型。比如说一个浮点数同样拥有特征和行为。两者之间不同的是,程序员所定义的类是来解决问题的,而不是强迫性的使用现有的用来描述机器存储单元的数据类型。通过添加新的数据类型,你可以按你的需要来扩展编程语言。编程系统会很乐意接受着这些新的类,并且对它们会给以监护和类型检查,就像内置类型一样。
面向对象方法并不局限于构建模拟系统。不管你是否认可,所有的程序都是对你所设计的系统的模拟,OOP技术的使用可以轻而易举的将原本难以解决的问题转变为简单的解决方案。
每当一个类建立起来,你可以随心所欲的创建这个类的对象,然后对这些对象进行操作,就好像他们是你正在求解的问题中的元素。事实上,在问题空间中的元素和解空间中的对象之间创建一个一对一的映射,是面向对象编程的一大挑战。
但是,怎样才能得到有用的对象呢?这里必须有一个请求对象作某些事情的途径,好比完成一次业务,在屏幕上画些图画,或者打开一个开关。每个对象只能满足特定的请求。你可以通过对象的接口来定义这些请求,接口决定类型。下面那电灯泡作为一个简单的例子:
Light lt;
lt.on();
这一接口定义了你可以对这一特定的对象提出什么样的请求。然而,必须有相应的代码来满足这一请求。这些代码以及隐藏的数据,构成了具体实现。以面向过程的编程观点,这并没有多么的复杂。一个类型拥有一个与每一个可能的请求项关联的函数,你对对象作出特定的请求,此时便调用了函数。这个过程经常被简称为对一个对象“发送消息”(发出请求),然后这个对象自己决定该做些什么(执行代码)。
在这里,类型/类的名称是Light,特定的Light的对象的名称是lt,你可以对一个Light对象发出请求:点亮,关闭,调亮或调暗。通过声明一个名为lt的对象,你创建了一个Light对象。如果你想对这个对象发送消息,列出对象名,加一个句点( .)然后加上消息请求的名称。从预定义来的用户的角度来说,这差不多就是面向对象编程的全部。
上面列出的图表是依照统一建模语言(Unified Modeling Language, UML)的格式绘制的。每一个类表示为一个盒子,类名在盒子的顶部,相关的数据成员在盒子中部,成员函数(属于这一对象的函数,可以接受你发给这个对象的任何信息)在盒子的底部。通常地,UML设计图中仅仅显示出类名和公共成员函数名,所以中部的内容不会显示。如果你只关心类名,那么底部也不需要显示。
隐藏的具体实现
将编程领域划分为类创建者(那些创建新的数据类型的人)和客户端程序员(类的用户,这些用户在自己的程序中使用类创建者所创建的数据类型)。客户端程序员的目标是集满他的“类工具箱”来进行快速开发。而类创建者的目标是构建类,他们将这些类中的客户端程序员所需要的部分暴露给程序员,而隐藏起了其他所有的内容。为什么这样子?因为一旦具体实现被隐藏了,客户端程序员就不能使用它了,也就是说,类创建者在不需要考虑外来的不利因素的前提下,可以放心大胆的修改那些隐藏的部分。这些隐藏的部分通常代表着对象内部脆弱的部分,而这些部分极易被那些二把刀的客户端程序员所破坏,所以说对具体实现的隐藏可以减少程序的bug。隐藏具体实现这一概念是极其重要的。
在任何关系中,为所有相关部分都设定边界是很重要的。当你创建了一个库,你便与客户端程序员(他同样也是个程序员,但是是一个利用你的库来构建他自己的程序程序员,也许他在构建一个更大的库)建立了某种联系。
如果所有的类成员对任何人都是可用的,那么客户端程序员可以对类为所欲为,没有规则可以约束他们。即使你不希望客户端程序员不能直接直接修改你的类的成员,如果没有访问限制就没有任何途径来防止其发生。对外部世界来说一切都是裸露的。
所以设定访问权限的第一个理由就是要使客户端程序员不能够接触到数据类型(而不是用户用来解决特定问题的接口的一部分)的内部机制,这样做实际上为用户提供了一项有用的服务,因为他们能够方便的分辨出哪些部分对他们来说是重要的,而哪些是可以忽略的。
设定访问权限的另一个原因是使得库设计人员可以修改类的内部工作机制而不会影响到客户端程序员。举例说,你可能使用了一个简单的方法实现了一个降低开发难度的类,然而在不久以后你发现你需要对其重写已获得更快的速度。如果接口和实现被清晰的分隔开并且受到保护,你就可以轻松的完成这一共做了,只需要用户重新连接以下就行了。
C++使用三个显性的关键字来设定类的边界,它们是:public,private,protected。他们的用法和含义非常的直截了当。这些访问权限设定符决定什么人可以使用其后边所跟随的成员。public是指后边的成员对于所有的用户都是可用的。另,private关键字,是指除了你自己之外任何人都不可以访问那些成员,该类型的创建函数,在这个类的成员函数之中,private 是一堵隔开你和你的客户端程序员的防火墙。如果有人试图访问一个private成员,他们会得到一个编译时错误。protected与private的作用相仿,只有一处不同点,继承的类可以访问protected成员,但是private不行,有关继承的概念将会在下一节讲解。