摘要
本文综述面向对象,尤其是面向对象编程的基本概念和一些其它编程范型的比较,并指出了现有初学者的一些常见误区。
I.面向对象(Object-Oriented, OO)综述
公认的面向对象是一种“思想”,更精确地说是一种方法学(methodology)。面 向对象编程(OOP)、面向对象分析(OOA)、面向对象设计(OOD)等范畴是对此的衍生。容易理解,OOP指使用OO的方法进行编程;OOA和OOD 分别指使用OO的方法进行系统分析与设计(可以合称OOAD),是OO方法学在软件工程上的应用。对于使用OO方法学的软件开发,OOP是基础也是具有更 强的普遍性(即便使用OO方法,也并非所有的软件都有必要使用系统化的OOA和OOD进行开发),通过OOP可以反映OO在编程实践中的重要应用,因此本文着重论述OOP的有关内容。关于OOAD,读者可以在了解OOP的基础上自行学习。
II.编程范型(programing paradigm)
编程范型是计算机编程中的一种基本方式[en.wiki:programming paradigm]。OOP和命令式编程(imperative programing)、函数式编程(functional programing)、逻辑编程(logical programing)并列,是当前主流的编程范型[Kurt Nørmarks]。此外还有结构化(structured)、声明式(deriective)、面向方面(aspect-oriented)、数据驱动 (data-driven)、泛型(generic)、并行(parallel)、元编程(metaprograming)等各种范型。
应该注意的是,这些范型并不都是同一层次上的风格,且由于分类方法的不同,不都是互斥的。当然也有些范型是对立的:结构化与非结构化(non-structured),但这是少数。因此通常在同一段程序中使用了一种以上的编程范型,只强调其中的一部分。
III.结构化编程(structued programing)、命令式编程(imperative programing)和过程式编程(procedural programing)
早期的程序没有强调任何范型,是非结构化的。结构化程序由子程序的执行、选择、迭代构成,无需跳转。
命令式编程是一种重要的编程范型。它和声明式编程相对,强调特定路径执行的步骤(控制流)使程序的状态向预期改变,而非和执行路径无关的计算逻辑的表达。大部分硬件实现的体系结构都直接支持命令式范型。可执行的语句作为语言特性,是支持命令式编程的重要特征。
过程式编程有时被看作是命令式编程的同义词,也可以表示一种基于结构化编程和过程调用(procedual call)的编程范型[en.wiki Procedural programming]。过程(或例程、子例程、方法、函数——注意不要和函数式编程混淆)在这里是可以通过调用这一手段被重复执行的程序片段,作为语言特性,是支持过程式编程的语言的重要特征。
下文中提及的过程式编程都指第二种含义,而第一种直接称为命令式编程。
无论是命令式还是过程式的编程范型都被许多编程语言广泛支持。对于Pascal、C、C++、Java等语言,两者都是最基本的(使用时几乎无法避免的)范型,而结构化结构化通过这两个范型的上层被体现。
IV.作为编程范型(programing paradigm)的OOP
OO程序可以看成一系列对象的交互,而不是如传统的过程式编程那样执行一系列任务(过程)。
如OO字面所说,对象(object)是其中的一个要素。
OOP中每个对象都有能力接收、处理和向其它对象发送消息(message),可以被看作具有不同角色或职责的独立单元。对象的动作(或“方法(method)”)与之紧密相关。例如,OOP数据结构倾向于携带自身的操作(或至少从类似的对象或类“继承”)。
对于过程式编程,程序可以被分解为若干过程。OOP的做法不同——它分解程序为若干对象和对象的交互(尽管其中的“方法”仍然可以扮演过程式编程中的过程的角色)。
传统的过程式编程中过程对数据的访问是不加外部限制的,而OOP的做法不同——它使用访问权限控制等特性,强调封装,鼓励程序员使某一部分的数据仅可被特定的程序片段(过程)访问,以减少可能发生的错误。
对象和消息是OOP的核心。OOP和对类型系统的抽象导致类(class)概念的出现。使用类作为核心特性的语言实现OOP的基于类的风格的 (class-based style)OO,这是支持OOP的语言中的主流,如C++、Java。与之不同的是基于原型的风格的(prototype-based style)OO,如ECMAScript。
当前流行的C++、Java等“主流面向对象语言”(事实上把C++称为面向对象语言并太不恰当,见下文),其实并不能算最符合OOP的基本要义。原始的OOP的观点,见面向对象之父Alan Kay的阐述[Alan Kay]。
OOP常具有如下几个特性:数据抽象、封装、消息、组件化、多态和继承。注意,这些特性并非同时提出的,因此并非都是OOP的必要组成部分;也并非OOP的专利。
应该指出的是,即便不使用直接的OOP特性,也可以进行OOP。例如C可以使用结构体模拟类。
V.语言相关的实例:Java和C++的OOP和其它范型的支持策略的比较
有些语言为特定的范型设计,鼓励用户使用特定的范型。如Java鼓吹的“简单”的“纯”OOP。但是通过上文可以知道,Java支持的面向对象只是一种 基于类的风格的OOP,并且血统远非纯净。另外,Java不支持多重(实现)继承而只支持接口继承,即便仅从基于类风格的OOP上来说也有缺陷(由于这 点,加上Java缺乏其它一些有用的特性,这给混入(mixin)等带来麻烦,也容易造成代码冗长)。语言构造也决定Java无法摆脱命令式和过程式编 程,在这个意义上并不“纯”。(尽管Java支持泛型,但功能过于薄弱,在此忽略。)使用好Java需要用户对OOP的较清晰全面的理解,事实上一点都不 “简单”。
另外一些语言不强调(甚至弱化)特定的范型,鼓励用户选择合适的范型,如C++。
关于OOP两者还有一些整体上重要的、原则性的不同。Java的OOP特性支持的设计试图减少OOP和OOAD的差距,使OOAD的结果尽量能直接OOP对应。初衷可以理解,但效果不见得好(取决于用户设计的合理性),反而几乎肯定增大了语义上的复杂性——这点的一个例子是“继承”不考虑private成员。而C++的设计事实上无视这一点。C++的class type并不见得就是OOAD意义上的class,它可以是类似Java里的interface或者和OOP无关的东西——如元编程用到的traits。这点可能导致学习上的困难,但增大了灵活性。
VI.结论:一些需要避免的误区
首先,所谓“面向过程”并不是公认的编程范型的名称,无法和过程式、OOP等相提并论。提出这个生造的术语可能只是效仿“面向对象”的构词,却忽略了其中的内涵。通常使用这个术语通常似乎是想表达“过程式编程”。在已有更精确和广泛接受的术语的情形下,不应该使用这种模糊的称为。
其次,关于语言和范型。尽管语言可以直接通过语言特性支持范型,但范型实质上是跟语言无关的。尤其对于C++而言,强调“支持面向对象”作为和其它语言的主要区别,是没有根据的。
参考文献
[en.wiki:]
[Kurt Nørmarks] people.cs.aau.dk/~normark/prog3-03/html/notes/paradigms_themes-paradigm-overview-section.html
[Alan Kay]Dr. Alan Kay on the Meaning of “Object-Oriented Programming” userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/doc_kay_oop_en