请输入您要查询的百科知识:

 

词条 面向对象化语言
释义

面向对象语言(Object-Oriented Language)是一类以对象作为基本程序结构单位的程序设计语言,指用于描述的设计是以对象为核心,而对象是程序运行时刻的基本成分。语言中提供了类、继承等成分。

基本概念

1.1 类与对象的初探

要我说,无论是面向过程的语言也好,面向对象的语言也罢,我首先要给他讲的都是类和对象!--------“这个世界是由什么组成的?”这个问题如果 让不同的人来回答会得到不同的答案。如果是一个化学家,他也许会告诉你“还用问嘛?这个世界是由分子、原子、离子等等的化学物质组成的”。如果是一个画家 呢?他也许会告诉你,“这个世界是由不同的颜色所组成的”。……呵呵,众说纷纭吧!但如果让一个分类学家来考虑问题就有趣的多了,他会告诉你“这个世界是 由不同类型的物与事所构成的”好!作为面向对象的程序员来说,我们要站在分类学家的角度去考虑问题!是的,这个世界是由动物、植物等组成的。动物又分为单 细胞动物、多细胞动物、哺乳动物等等,哺乳动物又分为人、大象、老虎……就这样的分下去了!

现在,站在抽象的角度,我们给“类”下个定义吧!我的意思是,站在抽象的角度,你回答我“什么是人类?”首先让我们来看看人类所具有的一些特征,这个 特征包括属性(一些参数,数值)以及方法(一些行为,他能干什么!)。每个人都有身高、体重、年龄、血型等等一些属性。人会劳动、人都会直立行走、人都会 用自己的头脑去创造工具等等这些方法!人之所以能区别于其它类型的动物,是因为每个人都具有人这个群体的属性与方法。“人类”只是一个抽象的概念,它仅仅 是一个概念,它是不存在的实体!但是所有具备“人类”这个群体的属性与方法的对象都叫人!这个对象“人”是实际存在的实体!每个人都是人这个群体的一个对 象。老虎为什么不是人?因为它不具备人这个群体的属性与方法,老虎不会直立行走,不会使用工具等等!所以说老虎不是人!

由此可见-------类描述了一组有相同特性(属性)和相同行为(方法)的对象。在程序中,类实际上就是数据类型!例如:整数,小数等等。整数也有 一组特性和行为。面向过程的语言与面相对象的语言的区别就在于,面向过程的语言不允许程序员自己定义数据类型,而只能使用程序中内置的数据类型!而为了模 拟真实世界,为了更好的解决问题,往往我们需要创建解决问题所必需的数据类型!面向对象编程为我们提供了解决方案。

1.2 内置数据类型与函数:

计算机程序在存储数据时必须跟踪3个基本属性为:

1. 信息存储在何处;

2. 存储的值是多少;

3. 存储的信息是什么类型的;

让我们来看看编程语言的内置数据类型都有哪些!(呵呵,这个不大好说,因为每门语言都有自己独特的数据类型,但这毕竟是少数,比如在JAVA中有 byte类型的数据,而在C++中就没有,希望你能举一反三!)比如整数”int ”,浮点类型的数据”float”!字符串”String”,以及数组还有结构体等等。然而在写程序的时候,根据需要我们会创建一个类型的变量或常量,例 如:由于我们需要创建一个整形的变量i为5,我们就可以这样做,int i = 5;而根据需要我很有可能改变i的值,也就是从新给它赋值,比如让它等与6,就可以在所需的地方改成i = 6;由此我们知道,在“值”上可以发生变化的量就叫变量。不会发生变化的量就叫做常量了,在C++中用count关键字来声明,而在JAVA中则使用 final关键字来声明。由于不同语言的声明格式不一样,这里就不做一一介绍了,详细的内容清查阅相关书籍!

在这里我们主要讨论一下函数,我们可以把函数想象成一个“实现某种特定功能的黑匣子”-------这个功能是由你来设定的,举个例子来说:现在我问 你“2+3等于多少”?我相信你能很快的回答我等于5。让我们来分析分析这句话包含什么信息!首先我要把你的大脑想象成是一个黑匣子,我并不知道也没有必 要知道你的大脑是如何工作的(也就是怎么运算的),我关心的只是我传给你的是什么信息?你对信息做了哪些处理? 以及你返回给我的是什么信息?需要提醒你一下的是每个方法都会返回一个信息给调用者的,除了构造函数外(稍候我会作详细的介绍)。我现在需要把自己当作是 一名程序员,而你呢?当然就是计算机了!计算即可没有人那么聪明,它只会按事先约好的特定的格式运行,我想让它具有如上所述的功能,我就要先定义这个黑匣 子!首先我要告诉这个黑匣子会有两个整数值给你(这就是所谓的参数,是程序员需要给黑匣子的信息),然后就要定义这个黑匣子内部实现这两个整数相加的运算 (这就是黑匣子对数据所做的加工,根据需要,你可以做任何的加工。)。最后再标注它返回给我一个同样是整型的数值(这是黑匣子返回给程序员的信息)。一个 函数就这样定义完了,让我们来看看书写格式:

int addnum(int x,int y){

return x+y;

}

具体的含义是这样的:

int /*返回值类型*/ addnum /*方法(黑匣子)名称*/ (int x,int y/*传入的参数*/){

return x+y; /*内部是想方法(实现相加运算,)并用return返回给调用者结果*/

}

首先请注意上明的“return”语句!return 关键字的含义是向调用者返回紧跟在它后面的信息!就像上面一样,因为我问你,你才会回答我,如果我不问你,你就不用回答我的!在计算机中也一样,定义好这 个函数在哪里调用呢?我只能告诉你,哪里需要就在哪里调用!当然,你可以根据需要去更改参数、返回值以及内部实现,具体到如何定义如何调用你只好去参考相 关的资料了!在这里我只是给你一个思想!

有时你会遇到这样的问题,我让你记住,我的年龄是20岁!从字面上理解,你并没有给我返回信息!然而事实上,你确实给我返回了信息,信息的内容是“无信息,也就是无返回值类型void”。具体的程序如下: int myAge = 0;

int a=20;

void remAge(int a){

myAge=a;

}

具体的函数说明如下:

int myAge =0; //定义并初始化我的年龄为0;

int a=20; /*定义变量a等于20*/

void /*返回值类型为无返回值类型*/ remAge /*函数名称*/(int a /*传入的参数*/){

myAge=a; //内部实现方法,注意,没有return返回!!!

}

关于函数的话题还有很多很多,这里就不一一介绍了,我的目的是让你知道函数是怎么一会事儿!为下面的讨论作铺垫!

1.3 指针以及引用:

指针及引用是在C++中有的,JAVA中没有。JAVA中取消了对内存的操作,随之而来的事也取消了操作符重载的操作。不过在稍候我还是会介绍一些操 作符重载的功能等。引用主要还是用在函数参数的传递上。所以我在这里就不做过多的介绍了。他们很实用,有兴趣的同学可以参阅C++相关书籍。

类和对

class cell is

var contents: Integer :=0;

method get(): Integer is

return self.contents;

end;

method set(n:Integer) is

self.contents := n;

end;

end;

一个类是用来描述所有属于这个类的对象的共同结构的。这个cell类表示的对象拥有一个叫做contents的整数属性(attribute),这个属性被初始化成0。它还描述了两个操作contents的方法。Get和set. 这两个方法的内容都是很直观的。Self变量表示这个对象自己。

对象的动态语义可以这样理解:

一个对象在内部被表示为一个指向一组属性的指针。任何对这个对象的操作都会经过这个指针操作对象的属性和方法。而当对象被赋值或被当作参数传递的时候,所传递的只是指针,这样一来,同一组属性就可以被共享。

(注, 有些语言如C++, 明确区分指向属性组的指针和属性组本身,而一些其它的语言则隐藏了这种区别)

对象可以用new从一个类中实例化。准确地说,new C分配了一组属性,

并返回指向这组属性的指针。这组属性被赋予了初始值,并包括了类C所定义的方法的代码。

下面我们来考虑类型。对一个new C所生成的对象,我们把它的类型记为InstanceTypeOf(c). 一个例子是:

var myCell: InstanceTypeOf(cell) := new cell;

这里,通过引入InstanceTypeOf(cell), 我们开始把class和type区分开来了。我们也可以把cell本身当作是类型,但接下来,你就会发现,那样做会导致混淆的。

面向对象的程序设计(OOP)是结构化语言的自然延伸。OOP的先进编程方法,会产生一个清晰而又容易扩展及维护的程序。一旦您为您的程序建立了一个对象,您和其他的程序员可以在其他的程序中使用这个对象,完全不必重新编制繁复的代码。对象的重复使用可以大大地节省开发时间,切实地提高您和其他人的工作效率。

方法解析

给出一个方法的调用o.m(……), 一个由各个语言自己实现的叫做方法解析的过程负责找到正确的方法的代码。(译者按:是不是想起了vtable了?)。

直观地看,方法的代码可以被嵌入各个单个对象中,而且,对于许多面向对象语言,对属性和方法的相似的语法,也确实给人这种印象。

不过,考虑到节省空间,很少有语言这样实现。比较普遍的方法是,语言会生成许多method suite, 而这些method suite可以被同一个类的对象们所共享。方法解析过程会延着对象内指向method suite的指针找到方法。

在考虑到继承的情况,方法解析会更加复杂化。Method suite也许会被组成一个树,而对一个方法的解析也许要查找一系列method suite. 而如果有多继承的话,method suite甚至可能组成有向图,或者是环。

方法解析可能发生在编译时,也可能发生在运行时。

在一些语言中,方法到底是嵌入对象中的,还是存在于method suite中这种细节,对程序员是无关紧要的。因为,所有能区分这两种模式的语言特性一般在基于类的面向对象语言中都不被支持。

比如说,方法并不能象属性一样从对象中取出来当作函数使用。方法也不能象属性一样在对象中被更新。(也就是说,你更新了一个对象的方法,而同一个类的其它对象的该方法保持不变。)

子类和继承

子类和一般的类一样,也是用来描述对象的结构的。但是,它是通过继承其它类的结构来渐进式地实现这个目的。

父类的属性会被隐式地复制到子类,子类也可以添加新的属性。在一些语言中,子类甚至可以override父类的属性(通过更改属性的类型来实现)

父类中的方法可以被复制到子类,也可以被子类override.

一个子类的代码的示例如下:

subclass reCell of cell is

var backup: Integer := 0;

override set(n: Integer) is

self.backup := self.contents;

super.set(n);

end;

method restore() is

self.contents := self.backup;

end;

end;

对有subclass的方法解析,根据语言是静态类型还是动态类型而有所不同。

在静态类型的语言(如C++, Java)里,父类,子类的method suite的拓扑结构在编译时就已经确定,所以可以把父类的method suite里的方法合并到子类的method suite中去,方法解析时就不用再搜索这个method suite的树或图了。(译者按:C++的vtable就是这种方法)

而对于动态类型的语言,(也就是说,父子类的关系是在运行时决定的),method suite就无法合并了。所以,方法解析时,就要沿着这个动态生成的树或有向图搜索直到找到合适的方法。而如果语言支持多继承,这个搜索就更复杂了。

类型信息

虽然subsumption并不改变对象的状态,在一些语言里(如Java), 它甚至没有任何运行时开销。但是,它却使我们丢掉了一些静态的类型信息。

比如说,我们有一个类型InstanceTypeOf(Object), 而Object类里没有定义任何属性和方法。我们又有一个类MyObject, 它继承自Object。那么当我们把MyObject的对象当作InstanceTypeOf(Object)类型来处理的时候,我们就得到了一个什么东西也没有的没用的空对象。

当然,如果我们考虑一个不那么极端的情况,比如说,Object类里面定义了一个方法f, 而MyObject对方法f做了重载,那么, 通过dynamic dispatch, 我们还是可以间接地操作MyObject中的属性和方法的。这也是面向对象设计和编程的典型方法。

从一个purist的角度看(译者按,很不幸,我就是一个purist), dynamic dispatch是唯一你应该用来操作已经被subsumption忘掉的属性和方法的东西。它优雅,安全,所有的荣耀都归于dynamic dispatch!!! (译者按,这句话是我说的)

不过,让purist们失望的是,大部分语言还是提供了一些在运行时检查对象类型,并从而操作被subsumption遗忘的属性和方法。这种方法一般被叫做RTTI(Run Time Type Identification)。如C++中的dynamic_cast, 或Java中的instanceof.

实事求是地说,RTTI是有用的。(译者按,典型的存在就是合理的强盗逻辑,气死我了!)。但因为一些理论上以及方法论上的原因,它被认为是破坏了面向对象的纯洁性。

首先,它破坏了抽象,使一些本来不应该被使用的方法和属性被不正确地使用。

其次,因为运行时类型的不确定性,它有效地把程序变得更脆弱。

第三点,也许是最重要的一点,它使你的程序缺乏扩展性。当你加入了一个新的类型时,你也许需要仔细阅读你的dynamic_cast或instanceof的代码,必要时改动它们,以保证这个新的类型的加入不会导致问题。而在这个过程中,编译器将不会给你任何帮助。

很多人一提到RTTI, 总是侧重于它的运行时的开销。但是,相比于方法论上的缺点,这点运行时的开销真是无足轻重的。

而在purist的框架中(译者按,吸一口气,目视远方,做深沉状),新的子类的加入并不需要改动已有的代码。

这是一个非常好的优点,尤其是当你并不拥有全部源代码时。

总的来说,虽然RTTI (也叫type case)似乎是不可避免的一种特性,但因为它的方法论上的一些缺点,它必须被非常谨慎的使用。今天面向对象语言的类型系统中的很多东西就是产生于避免RTTI的各种努力。

比如有些复杂的类型系统中可以在参数和返回值上使用Self类型来避免RTTI. 这点我们后面会介绍到。

方法特化

在我们前面对subclass的讨论中,我们采取了一种最简单的override的规则,那就是,overriding的方法必须和overriden的方法有相同的signature.

但是,从类型安全的角度来说,这并不是必须的。应用我们前面讨论的协变和反协变的知识,我们完全可以让方法的返回类型协变,让方法的参数类型反协变。

这样,只要A <: A’, B’ <: B, 下面的代码就是合法的:

class c is

method m(x:A):B is … end;

method m1(x1:A1):B1 is … end;

end;

subclass c’ of c is

override m(x: A’):B’ is … end;

end;

我们暂时不允许属性的协变。因为只有immutable的属性才是协变的。允许对属性的修改使得属性都是invariant的。

特殊变量self这里有一个有趣的属性,它是一个参数,但它却是协变的。这种特殊特性是由于self变量只能隐式地由编译器传入,所以避免了协变参数的不安全性。

还有一点有趣的地方是,上面的协变发生在override的时候,也就是,子类要改写父类的方法的时候。但是,在继承时,参数和返回类型的变化规律就又是另一回事了。

比如说,下面这个例子:

class c is

method m(x:A):B is … end;

method m1(x1:A1):B1 is … end;

end;

subclass c’ of c is

inherit m(x: A’):B’;

//这里,方法m的代码被继承,子类只是重定义方法m的接口signature

end;

那么,这里,参数就是协变的,而返回类型却是反协变的了。

这里,从另一个侧面,我们看到了subtyping (通过override), 和subclassing (通过inheritance) 的本质上的区别。

随便看

 

百科全书收录4421916条中文百科知识,基本涵盖了大多数领域的百科知识,是一部内容开放、自由的电子版百科全书。

 

Copyright © 2004-2023 Cnenc.net All Rights Reserved
更新时间:2024/11/15 10:34:36