当前位置:编程学习 > php >>

什么是面向对象编程?

答案:译者序
不要将本文简单地视为是对C++特征的一个介绍。它的意义在于,一方面介绍了编程风格的演变,以及这种演变背后的动机
。另一个方面,它特别澄清了基于对象的(OB)和面向对象(OO)的异同,这是具有很大意义的。我们可以看到,
不管是OB还是OO,都不过是一种程序的组织形式。 这在很大程序上指出了OO着眼于解决什么样的问题
(程序如何组织才能有弹性,容易重用和理解),而不解决什么问题(数据结构的设计,算法的设计)等等。

摘要
“面向对象编程”和“数据抽象”已经成为常用的编程术语,然而,很少有人能够就它们的含义取得一致的认识;本文以Ada,C++,Module 2,Simula和Smalltalk等语言为背景对此给出一个非正式的定义。基本的想法是将“支持数据抽象”等同于定义和使用新数据类型的能力,而将“支持面向对象编程”等同于对类层次的表达能力。同时,还讨论了通用编程语言为支持此种编程风格而必须提供的机制。文中虽然采用C++来表述问题,但其讨论的范围并不仅限于这个语言。

1 介绍
并不是所有的语言都是面向对象的。一般认为,APL,Ada,Clu,C++,LOOPS和Smalltalk是面向对象的,我也曾经听说过关于使用C, Pascal,Module-2,和CHILL进行面向对象设计的讨论。那么是否可以尝试使用Fortran和Cobol来进行面向对象设计呢?我认为那也一定是可行的。在很多圈子里,“面向对象”已经成为“优秀”的高科技代名词,在商业出版领域可以看到有以下的三段论:
Ada是优秀的
面向对象是优秀的
所以Ada是面向对象的
本文从通用编程语言的角度出发陈述了“面向对象”技术的概貌:
第2节比较了数据抽象和面向对象之间的异同,也将它们和其他的编程风格做了区分;同时,指出了为了支持不同的编程风格所需的重要机制。
第3节陈述了为高效地支持数据抽象所需的语言机制。
第4节讨论了支持面向对象所需的设施。
第5节陈述了传统硬件体系结构和操作系统对于数据抽象和面向对象编程施加的限制。

文中例子程序使用C++来书写,这部分是出于介绍C++的目的,部分是因为C++是少数几个同时支持数据抽象,面向对象程序设计和传统编程风格的语言。本文不讨论为支持特定高层语言特性而涉及的并发性和特殊硬件支持。

2.编程风格(Programming Paradigms)
面向对象编程是一种用来针对一类问题编写优质代码的编程技术。一个语言称为是“面向对象”的如果它支持(Support)面向对象风格的编程。
在这里存在一个重要的区别。一个语言称为是“支持”某种风格的编程技术的,如果它提供了便于实施(方便地,安全地和高效地)该种风格编程的手段;反之,如果需要使用额外的技能和手段来获得基于某种风格的编码,则这个语言就是不“支持”该种编程风格的,我们只能说这个语言“使能”(Enable)了某种编程风格。举例来说,人们可以使用Fortran编写结构化程序,使用C语言编写类型安全的程序,在Module-2中使用数据抽象技术,但是,这些任务都具有不必要的困难性,因为这些语言都不“支持”那些编程风格。
对于某种编程风格的支持不仅意味着语言提供明确的并且可以直接使用的编程手段,而且还意味着在编译时间和运行时间提供某种检查,以防止代码无意中偏离了该种风格。类型检查是一个特别明显的例子,二义性检查和运行时间检查也可以扩充语言支持特定编程风格的能力。同时,象标准库和编程环境等等都可以增强这种支持。
并不一定说一个语言如果支持了某种特性,则它就一定优于其他没有支持该特性的语言。在这里存在着太多的反例。重要的不是一个语言具有多少特性,而是它具有的特性是否能够在特定的领域内足以支持特定的编程风格。

1.所有的特性必须是清晰,优雅地集成进语言的。
2.通过组合使用这些特性必须足以获得解决方案,而不再需要使用其他特性。
3.假冒的和“特殊目的”的特性必须尽可能的少。
4.所有的特性都不能在那些不使用它们的程序中强加上过多的开销。
5.用户只需要了解那些在程序中被明确使用的特性所构成的语言子集就可以编写程序。

最后两点可以概括为“程序员不会被他们不了解的东西伤害”。如果对于一个特性是否有用存在任何疑问,则该特性就最好被抛弃。在语言中加上一个特性要远比从中或者从其文献中去掉一个容易得多。
以下将罗列一些编程风格以及支持它们的核心语言机制,但对此并不打算讨论得过于深入和繁琐。

2.1 过程化编程
最初的(可能也是目前最常用的)编程风格是:
决定需要那些过程
使用能够得到的最好的算法
设计的重点在于处理过程和执行运算的算法,语言为此提供了将参数传递给函数以及从函数中返回值的机制。和这种思维方式相关的文献集中讨论了传参的不同方式,区分不同参数的方式,以及各种不同的过程(过程,函数,宏)等等。Fortran是最早的过程语言,Algol60,Algol68,C和Pascal是一些后继的过程语言。
平方根函数是个典型的例子,它简单地产生传入参数的平方根。为此,该函数执行一个简单的数学运算:
double sqrt(double arg)
{
//the code for calculting a square root
}

void some_function()
{
Double root2 = sqrt(2);
}
从程序结构的角度来看,函数理清了算法之间的杂乱关系。

2.2 数据隐藏
随着时间的推移,程序设计的重点从重于过程设计转向重于对数据的组织,这反映了程序规模的增长。数据和直接操作数据的一集函数合称为一个模块。程序设计的风格变为:
决定需要那些模块
分解程序,使得数据隐藏在不同的模块之中
这种风格被称为“数据隐藏规则”。而在那些不必将数据和与它相关的过程绑定到一起的场合可以只使用过程程序设计风格。特别地,那些用来设计“好的过程”的技术现在可以应用到模块之内的每个过程之上。最常见的例子是定义一个堆栈模块,设计时有以下问题需要解决:
1.为堆栈模块提供一个用户接口(例如,函数 push()和pop() )
2.保证堆栈的表示(例如,一个元素的阵列)只能通过模块的接口来访问
3.保证堆栈在它第一次被访问之前执行过初始化

以下是一个不甚严格的堆栈模块的外部接口:

//declaration of the interface of module stack of charater
char pop();
void push(char);
const stack_size = 100;

假定这个外部定义保存在stack.h文件之中,而其模块内部表示如下:
#include "stack.h"
static char v[stack_size];
static char* p = v;
char pop()
{
//Check for underflow and pop
}

void push(char c)
{
//check for overflow and push
}

要将堆栈的表示修改为链表是很方便的,用户不能访问堆栈的内部表示(因为v 和p 已经被声明为static的,因此只能在声明它们的模块内部引用它们)。可以象这样使用这个堆栈模块:
#include "stack.h"
void some_function()
{
char c = pop(push('c'));
if( c != 'c' ) error( "impossible" );
}

Pascal没有提供令人满意的设施来实施这种绑定。将一个名字和程序的其它部分隔离开来的唯一办法是使它局部于一个过程之内,这导致了奇怪的过程嵌套以及对于全局数据的过度依赖。
C语言的表现略好一些,在上面所述的例子之中,可以将数据和与它相关的过程保存在同一个文件之中以形成模块,由此程序员可以控制哪些名字是全局可见的(被声明为static的名字只在本模块内可见)。由此,C语言可以在一定程度上支持模块化;然而C缺乏使用这种机制的一般性框架,同时,通过static控制名字访问显得过于低级。
Pascal的一个后继语言,Module-2,走得更远一些。它形式化了模块这个概念,提供了一些基本的语言构成,如良定义的模块声明,对于名字范围的明确控制(import,export), 模块的初始化机制,以及一组公认的对这些机制的使用方式。
C和Module-2在这个领域内的区别可以概括为,C只是“使能”了将程序分解为模块,而Module-2则“支持”这种技术。

2.3数据抽象
模块化编程发展成为将某种类型的数据集中置于一个类型管理模块的控制之下的编程风格。如果有人需要两个stack,则他可能设计出一个具有如下接口的堆栈管理模块:

class stack_id; //stack_id is a type
//no details about stacks or stack_ids are known here
stack_id create_stack(int size); //make a stack and return its identifier
destroy_stack(stack_id);
void push( stack_id,char)
char pop(stack_id)
相对于以往那些无结构的混乱风格,这当然是一次重大的改进。然而,通过这种方式实现的“类型”又明显地和语言的内建类型有区别。每一个类型管理模块都必须分别定义自己的机制来生成自己的“变量”;这里没有什么明确的方法可以赋予变量以标识符,也不可能让编译器和编程环境了解变量的名字。同时,没有办法让这些变量服从常用的变量作用域规则和参数传递规则。
通过模块机制建立起来的类型在很多重要的方面都和内建类型存在区别,同时,它获得的支持也远比内建类型获得要低级得多。例如:
void f()
{
stack_id s1;
stack_id s2;

s1 = create_stack(200);
//Oops: forgot to create s2

shar c1 = pop(s1,push(s1,'a'));
if( c1!='c') error("impossible" );
char c2 = pop(s2,push(s2,'a'))
if( c2!= 'c') error( "impossible");

destroy(s2);
//Oops,forgot to destroy s1
}

换言之,支持数据隐藏风格的模块概念只是使能了数据抽象,但它不支持这种风格。

Ada, Clu和C++等语言通过允许用户定义和内建类型行为相似的“类型”来解决这个问题。这种“类型”通常称为“抽象

上一个:近观Web服务器-认知篇
下一个:高新技术的理念与经营思想

CopyRight © 2012 站长网 编程知识问答 www.zzzyk.com All Rights Reserved
部份技术文章来自网络,