面向对象的基本特点
Simula包含了大量我们现在仍在使用的面向对象的特点,除了前面提到的类、对象以外,还有以下特点:
一 动态查找选择对象的活动记录操作;2
一 抽象在Simula晚期版本中具备,但在Simula 67中没有;
一 子类型源于类型与类相关的方式;
一 继承类向前兼容的形式,包括在子类中重新定义类的一部分。
尽管Simula 67中没有区别类的public和private机制。但晚期语言允许使用“protected”,它允许子类型可见(其他类不允许使用),以及“hidden”,即使子类也不能只用它。除了以上特点外,Simula还包含了一些其他大多数面向对象语言不包含的以下特点:
一 inner使用inner关键字,子类还可以调用相关父类的代码;
■ inspect和qua在运行中可以测试对象类型,从而执行相应类型的代码。inspect是强类型语言,而qua是正确性检查及类型校验。
所有这些特点在接下来的章节中都将进行详细讨论,一些在其他语言中出现而没有在早期的Simula版本中出现的有:多重继承、类变量和self/super机制。
基本特征五个概念要理解Smalltalk首先必须理解五个概念,即对象(object)、消息(message)、类(class)、实例(instance)和方法(method),它们是互相以一种循环的方式来定义的。只要理解了这五个概念,Smalltalk的其余部分也就非常好理解了。
对象——在Smalltalk中,任何东西都是对象,数据结构、物理装置、概念、文件、可执行程序段、以及源代码程序段都是对象,连支持环境的组成部分,如编译工具和调试工具等也都是对象。一切都需要按对象和对象行为的方式来思考和行动。
类——类是定义某种对象特性的对象。类有名字,它表明了它所代表的对象的类型。由某个类代表的对象被称作该类的实例。类按超类、子类层次排列,其中子类是比超类更特殊的类,它能继承超类的特性。类的协议描述提供了建立该类的实例的途径。对象所能响应的所有动作均在它的类描述中定义。
实例——实例是某个类对象,它的特性由该类的协议描述来定义。实例所能响应的动作由它的类协议定义。
消息——消息是发送给某个对象的动作标识符。它指示对象执行某些动作。只有当消息包含在某个对象的类协议描述中时,该对象才能响应该消息。某个类的实例能响应的所有消息都必须在该类或它的超类的协议描述中定义。消息可由类从它的超类中继承。
方法——方法是消息的详细实现。类的协议描述应包括它的实例所能响应的每个消息的方法细节。方法精确地定义了对象如何响应消息。3
抽象在Smalltalk中的对象是抽象的封装体。抽象包括数据抽象和功能抽象。数据抽象的形式主要是私有和共享数据,它们定义了对象的特性。而功能抽象则采用了方法的形式,这些方法给出了某个对象如何响应消息的细节。对象所能响应的每个消息都对应着一个方法。
任何对象都是某个类的实例。而每个类都有名字并代表一种类型的对象。可以用给类名发送一条实例创建消息的方式来建立对象。某个对象的所有数据抽象和功能抽象均在其类描述协议中定义,包括用于实例创建的消息、私有数据、共享数据和方法。
最一般的对象是由一个称作为Object的类所代表的对象。Object是Smalltalk系统中所有其他类的超类。它的类描述协议包括消息和相应的方法、私有和共享数据、以及实例创建方法,这一切对于任何对象都是共同的。然而只要需要,子类也可以重载Object的任何方法、增加新的私有和共享数据。
在开发象Smalltalk这样的软件系统的过程中,定义它所支持的对象种类以及其相应的功能抽象是最主要的工作。Smalltalk映像是由200多种类组成的,这些类按照类别和依赖关系组成一个类层次。在映像中有些类是抽象类,它们有以下特性:
(1)没有任何对象是抽象类的实例,它们只能是抽象类的子类的实例;
(2)抽象类中的方法代表他的所有子类的公共协议。子类可以重载方法和增加新的数据;
(3)抽象类提供了一个逻辑层次组织。
例如,在smalltalk系统中只有两个Boolean对象。对象true是类True的唯一实例,对象falSe是类False的唯一实例。一个布尔表达式(比较或测试)的结果或是对象true或是对象false。对象tfue和faIse中的哪一个响应消息形成了smalltalk中控制的基础。
在smalltalk映像中类True和False是类Boolean的子类。Boolean没有实例,只是用来定义True和False的公共协议,因此Boolean是一个抽象类。在该映像中还存在其他的抽象类。
类True和False的协议描述列出了几个消息以及相应的用于控制的方法(功能抽象)。
封装smalltalk中对象的封装是基于个别对象的类描述协议的。特定类的协议定义了它的实例(对象)的特性。对象的特性包括描述性参数(其他对象)和对象如何响应消息的细节。
类描述协议包括许多基本元素,但并不是每个类都有所有这些元素。是否具有私有数据或共享数据依赖于该类所代表的对象的性质。方法的数量和类型依赖于该类的对象所必须响应的功能抽象有多么复杂和丰富。以下是类描述协议的基本元素:
定义——一段表明该类在类层次中位置的叙述,它同时也列出该类中私有、共享和公用数据对象的标识符。
私有数据——其值为该类的个别实例拥有的描述性参数(对象)。私有数据实例变量表示它只能由实例方法访问。如何访问私有数据对象由每个对象的类的协议决定。
共享数据——其值为该类的所有实例共享的描述性参数(对象)。共享数据由类变量表示,它可由实例方法和类方法两者访问。因此访问细节是由每个对象的类的协议控制的。
公用数据——其值为多个类共享的描述性参数(对象)。对公用数据的访问必须在类描述协议中特别表示出来。公用数据是用pool dictionaries表示的,因此,可由实例方法和类方法两者访问。但是,由于公用数据也是另一个类的实例,访问将受到该类提供的消息的限制。
实例方法——某个类的实例能响应的消息的实现细节。这些方法所代表的消息只能发给该类的实例。
类方法——某个类能响应的消息的实现细节。这些方法所代表的消息只能发该类名。类方法的典型用途是初始化类变量、创建某类的实例等。
继承面向对象的程序设计语言的要求之一就是必须支持继承。继承只有在类按层次排列时才有意义。子类继承超类的某些东西,包括私有数据、共享数据、实例方法和类方法。子类继承它的一切先辈,从直接超类一直到类Object。
如果某个子类没有任何同它的超类不同协议(包括数据和方法),它就没有存在的必要。因此子类必须有某些新的数据和方法。同时,子类也可以重新定义从它的超类继承来的方法,这种情况称为方法的重载,它是一种多态性。
smalltalk的继承规则如下:
(1)某个子类只从它的超类层次链上继承,从直接超类一直到类ob_ject;
(2)所有的类都是类object的子类,除了类object本身以外。类object没有超类;
(3)超类不继承子类;
(4)支持多重继承,这意味着一个类可被声明是一个以上层次链的子类,并且可以继承每个层次链的特性;
(5)一个子类可从它的超类继承私有数据、共享数据、实例方法和类方法;
(6)对继承的协议而言,访问私有和共享数据的规则同类描述协议的相同;
(7)除了继承的协议以外,子类还可以增加新的协议;
(8)子类可重载继承的实例和类方法。
多态性smalltalk中消息选择器的范围限于每个类和它的子类的类协议描述。这样,相同的消息选择器可存在于许多类中。当消息发送给对象时,其意义是由该对象所属类的协议描述所确定的。不同对象响应同一个消息选择器的能力称为多态性。
通过允许同一个消息选择器(表明一个特定的动作)被发送给不同对象,多态性大大地增强了软件的可读性。例如,消息printon:可发送给smalltalk系统中的任何对象,唯一的要求是printon:的细节必须包括在该对象所属类的层次路径上。从概念上来讲,printon:表明了一个特定的动作。这个概念对任何对象都是相同的,只是实现细节不同。
类协议由于类也是对象,因此它也有类,定义了一个metaclass。
metadass(元类)是一个类的类,它是唯一的且有着不同于其他类的特性。每当定义一个新类时,其元类自动被定义。每个类可以有不同的元类,这一事实使得所有的类无需有相同的协议。元类的主要特性如下:
在smalltalk映像中,每个类只有一个元类。
每个元类只有一个实例,即它所相应的那个类。
一个“类”对象的协议是包含在它的元类中的。类变量和类方法是一个类的元素协议描述的一部分。为了方便起见,将类描述和元类描述组合了起来。这两者均可用系统浏览器(system Browser)观察和修改。
任何一个元素并不是另一个元素的实例。相反,所有的元类都是一个叫做Meta—class类的实例。MetaclaSs是映像层次的一部分。
由于Metaclass是类(不是元素),它也有元素。Metaclass的元类也是Metaclass的实例。 .
一个名叫class的类是所有元素的抽象超类。对元类而言,它起的作用就象object对类起的作用是一样的。同时Class也是元类,也是Metaclass的实例。
元类也有一个层次,除了层次高一些以外,同类层次完全匹配。记住,Object是所有其他类的超类,包括元类。
继承和多态性规则同样适用于元类。
对象对象是一种可以循环描述的数据结构,每个对象都是某个类的实例。一个对象所属的类可以通过对其发送所有的对象都能理解的消息class来确定。
对象是封装的数据结构,储存在一个对象内部的数据只能通过消息来存取,但对象可以共享。
方法中的字self是指调用这个方法的消息的接受者。
变量smalltalk变量是用来存放对象的,方法是存储对象的指针。在表达式中,变量名可用来表示一个对象,方式是存储该对象的指针。变量在不同的时刻可存放不同对象的指针。当执行一个赋值表达式时,存放在变量中的对象指针可能发生改变。赋值语句只拷贝对象的指针而不是对象本身。
变量有私有的和共享的两种。私有变量只能由单个对象访问,以小写字母开头;共享变量可被多个对象访问,以大写字母开头。
变量共有三种类型:
(1)实例变量是对象的组成部分,它们在对象的整个生存期都存在;
(2)暂时变量是在某个方法执行时创建的,它们只在该方法执行的时间内存在;
(3)共享变量可由许多对象共享。如果不删除,它们将始终存在于系统之中。
实例变量每个对象都保存其内部状态,一个对象的私有存储器由只供自己访问的称为实例变量的部分组成。实例变量类似于其他语言中一个记录结构的域,或者是有一个名字或者是用一个整数索引来引用。有名实例变量可以通过其名字来访问,索引实例变量只能通过消息来访问(一般是at:put:带整数索引值)。类的每个成员都有自己的实例变量。
实例变量具有三种类型,即指针型、字型和字节型。属于同一个类的对象的所有实例变量具有同一种类型。大多数对象的实例变量用来存放指针,这个指针指向该对象。如果一个对象存放字或字节,则它的实例变量分别存放16位或8位值来表示基本的数据。
类对其成员对象既可以声明有名实例变量也可以声明索引实例变量。对于一个类的所有成员来说,有名实例变量的名字和数目是固定的。在同一个类的成员中索引变量的数目可以不同。例如:#(1 2 3)和#(up``down
)都是类Array的对象,但是它们的索引实例变量的数目不同,分别是3和2。具有索引实例变量的类用指定创建索引实例变量数目的消息来创建其新成员(一般使用带有一个整数变元的消息new:)。许多对象可用消息size返回其索引实例变量的数目。
只有调用了某个方法的消息的接受者的实例变量才可以用名字来表示。
暂时变量暂时变量包括方法变元、方法暂时变元以及代码块中的块变元。
方法变元被赋予调用该方法的消息的变元。方法暂时变元在方法调用时被初始化为nil,块变元被赋予代码块执行时消息value:的变元。
共享变量共享变量在共享字典集中定义,不同种类的共享变量在不同的共享字典中定义。所有的共享变量名都以大写字母开头,变量名和变量值一起放在一个Association对象中,这个对象也是放在共享字典中的。
系统字典Smalltalk是包含所有全局变量的共享字典,全局变量可被任何对象访问。
每一个类的类变量隐含地收集在相应类的共享字典中,类变量是作为类的规格说明的一部分来定义的。类变量只对本类、本类的子类、本类的实例和本类的子类的实例是可访问的。
共享字典变量也包含在共享字典中,它是全局变量。要使共享字典变量对某个类及其实例是可访问的,用户必须修改这个类的规格说明。
类类是Smalltalk的程序模块,它描述了数据结构(对象)、算法(方法)和外部接口(消息协议),因此提供了完整的问题求解能力。
每个对象都是某个类的实例,一个类的所有实例具有相同的结构(即:相同的实例变量,能够响应相同的消息,可用相同的方法)。
类也是对象,它存储在Smalltalk系统字典中的全局变量中。类名以大写字母开头,以便类可在表达式中引用。
类层次所有的类形成一个类层次,它由一个称为Object的根类和许多子类组成。每个类继承其超类的功能。类Object提供了所有对象所共有的行为,它包括对象标识符的打印方法、测试对象的类的方法以及拷贝对象的方法。每个子类都在其超类的基础上,通过添加自己的方法和实例变量来实现它自己的行为。
继承一个类继承其超类的所有的实例变量、类变量和方法。类变量的继承是指允许某个类定义的方法引用其超类定义的类变量。实例变量的继承是指允许某个类定义的方法引用其超类定义的实例变量,但是它还意味着这个类的实例对象中包括其超类的实例变量。
要确定方法能完成何种功能可通过两条信息来判断,即消息的选择器和消息接受对象的类。
首先,检查是否存在接受对象的类可用的方法,看一看是否该类中存在一个匹配这个消息选择器的方法,如果存在,则执行这个方法,如果不存在,则在接受对象的类的超类中再检查是否存在一个匹配这个消息的选择器。重复这一过程直到找到这个方法或到达层次链的顶端为止。在后一种情况下,出现编辑错误并且一个描述这种错误的消息被送到原来消息的接受对象上。
这里有一个接受对象的特殊的句法形式——super,它改变了消息查找的起始类,字super有两个含义:
(1)它同self表示的是同一个对象,引起含有super的方法执行的t削息的接受对象;
(2)它使消息查找从含有super在其中出现的方法的类的超类开始,而不是从接受对象的类开始。
向super发送消息的主要目的是能够使用在超类中的一个方法,该方法在其子类中是重新定义的。
类消息发送到类对象的消息是用来创建该类的一个实例和初始化类变量,最普通的创建一个新实例的消息是new和new:,有些类定义自己的创建实例的消息。
同所有的对象一样,类知道它们可响应哪些消息。对于其他对象来说,可用的方法是由对象的类确定的。类对象也属于一个“类”,称之为元类(metaclasS),它确定了类对象可响应的消息。
有三个与元类有关的重要的类:
(1)Metaclass——所有元类的类;
(2)class——类Metaclass的所有实例的超类;
(3)每个元类只有一个实例:以它为元类的类。
声明一个新类要想增设一个新类,首先要选择它的超类,并使它成为所选超类的子类,然后加上新的实例变量和方法。类是通过向新类(或要修改的类的超类)发送消息来定义的,该消息将以类的规格说明信息作为变元。要声明的类的信息如下:
类名。
类的对象是否存放指针、字或字节。
类的对象是否含有索引实例变量。
类的对象的有名实例变量名字。
所有类的对象可用的类变量名字。
该类的对象和其他类的对象可用的共享变量的共享字典的名字。
消息和方法smalltalk的处理都采用向对象发送消息的方式。消息是为了表示用户对对象的计算要求所使用的交互式语言。而方法又是对象响应消息而完成的算法。一个类协议有两部分——类方法和实例方法。
类方法实现那些发送到类的消息,类消息的接受者总是类对象,而不是类的实例。所有的类都是全局变量并可用它们的类名来引用。
实例方法实现那些发送到实例的消息,实例消息的接受者总是一个类的实例对象。
方法包含一系列smalltalk表达式,其中包括:
1.文字
#aSymbol #(1 2 4 16) ’magic`。
2.变量名
Smalltalk x replacementCollection
3.消息表达式
bag add:Stream next
100 factorial
array at:index+10 put:Bay new
4.代码块
[:x:y I x name