类型(type)以及类型系统的起源以及研究与发展是独立于OOP的。早在五十年代的FORTRAN语言编译器实现中,就已经采用类型系统作为类型检查的一种手段。广义的类型一般被定义为一种约束,也就是一种逻辑公式。而在对类型的研究过程中产生多种方法,比如[C&W 1985]等。而代数方法(algebraic approach)是一种非常好的建立类型的形式化规范的方法。代数中的一个类型对应于一系列元素,在它们之上定义代数操作。同时在此基础上二阶λ演算已经被用于继承和模板所支持的模型。在上面两种方法中,类型被认为是一系列满足确定约束条件的元素,更抽象的方式可以把一个类型当作规定一个约束条件,如果我们规定的约束条件越好,相对应的被定义元素的集合就越精密,所以逻辑公式(logical formulas)就成为描叙类型特征的最合适工具。在这里,我们不想深入的探究对于类型理论的各种不同的数学模型,我们需要明白的是类型(type)以及类型理论这个在编程语言中经常应用到的概念的内涵是极其丰富的,而其自身理论的发展并非局限于OOP之中,但当两者相结合的时候就对我们的程序观产生了巨大的影响。
在面向对象设计(OOD)中,“归类”是重要步骤,一个精心设计的类层次结构是则是OOD的重要成果。类的层次和界面定义得好,将造福软件系统的实现者、维护者和以后的扩展者:他们会惊喜地发现,许多错综复杂的关系在清晰的类型层次中不言自明;而失败的类层次结构则是灾难的来源:为了绕过不合理的类型设计带来的束缚,编码员不得不把各种能想到的技巧都用了上去[4]——包括强制的类型cast、直接对对象内存的访问等,而这些技巧往往和潜在的bug形影相随。
在数据结构的归纳和发展中,类型也扮演了重要的角色。ADT的引入是一个里程碑,早期的语言就开始struct(C)、record(Pascal)等复合结构类型为ADT提供支持。ADT是什么?抽象数据类型。
在程序设计语言中,类型的概念由来已久,而其内涵也在不断发展之中。语言对类型机制更好效率更高的支持成为语言成熟度的标志。OOP语言对类型的支持机制包括class、interface、inheritance、polymorphism,RTTI,各种cast等,这为编程带来了许多方便,因为所有概念在语言中都有了对应物。关于OOP语言中类型的形象阐释,请参见我写的《漫谈程序设计语言的选择和学习》(发表于《程序员》2001年10月刊)和与朋友合译的《Object Unencapsulated: Eiffel, Java and C++》1.6节。而在泛性程序设计(GP)概念中,所谓“分类学”也就是对类型的一套定义。而模板参数的constraint,则其实是“类型所需符合之类型”,不妨将其与OOP中interface之概念作一对照:一个class需实现某一interface,才可说其属于(is-a)一定类型。C++中无interface直接对应物[2],可这样表述:一个class需公有继承一个abstract class,则说其属于(is-a)该abstract class所定义之类型。而constrained genericity中,模板参数需符合某一constraint,该模板才能实例化。在GP和STL的著作中是这样表述的:模板参数(这是一个类型)叫model,其需符合的constraint(一个更为抽象的类型)叫做concept。对model更多的constraint叫做refinement。所以,concept-model-refinement可以和interface-class-inheritance对照理解。值得指出的是,Eiffel之父Bertrand Meyer在OOP经典著作Object-Oriented Software Construction 2/e中将泛型定义为类型参数化,并认为泛型技术和OOP中的继承与多态技术并列:泛型描述水平方向的类型关系;而继承则描述垂直方向上的类型关系。(我在《漫谈程序设计语言的选择与学习》一文中对此有具体阐释,见《程序员》2001年10月刊及http://zmelody.myrice.com/articles/pl.htm)。Bertrand认为泛型方法是经典OOP方法的补充,因此也可纳入OOP的范畴。)两者在实现上的不同是,C++中GP采用的是generative template实现方法,这是用空间换时间的方法,所以大量使用模板的程序常体积较大,但运行速度稍快于对应的OOP版本;而OOP则采用增加间接层的方法,增加了时间开销。另外还有一点不同: OOP是成熟的设计方法,interface、class、inheritance等都有语言元素直接对应,而GP的许多概念则缺乏语言级支持。
类(class),类型(type),接口(interface)
这三个概念是在OOP中出现频率最多,也最容易混淆的。而对于这三个概念的澄清也是文章写作的初衷。让我们先看看大师们对于这三个概念的描叙----
“The fundamental unit of programming in Java programming language is the class, but the fundamental unit of the object-oriented design is the type.while classes define types,it is very useful and powerful to be able to define a type without defining a class.Interface define types in an abstract form as a collection of methods or other types that form the contract for the type.” [Jams 2000]。
“In C++,A class is a user definite type”[B.S 1998]。
“A type is a name used to denote a particular interface……An object may have many types,and widely different objects can share a type.Part of an object’s interface may be characterized by one type ,and other parts by other types.Two objects of the same type need only share parts of their interface.Interface can contain other interface as subset.We say that a type is a subtype of another if its interface contain the interface of its supertype.Often we speak of a subtype inheriting the interface of its supertype”[Gamma 1995]
在其中,一共出现了四个概念:类(class),类型(type),接口(interface)以及契约(contract)。这里我们说到的类型和上面提到的类型有所不同,是狭义的OOP中的类型。为了理解这几个概念,我先划分出三个概念域:一个是针对现实世界的,一个是针对特定程序设计范型的(在这里就是OO设计范型),最后一个是针对编译器实现的。也就是说,在现实世界中的概念必须有一种手段映射到OO范型中去,而OO范型中的概念也应该在编译器实现中有相同的概念对应。由此,我们可以这样说,类是做为现实世界中的概念,而传统的OOPL都会提供class关键字来表示对现实世界模拟的支持。而接口,是作为OO程序设计范型中与类对应的一个概念。在OO设计中,我们所要做的就是针对接口进行设计和编程,而接口的实质含义就是对象之间的一种契约。而类型就是编译器实现中针对类和接口所定义的对应概念。可以这样说,类是现实世界中存在的客观概念,是唯物的。接口是设计人员定义出来的,存在于设计人员心中的概念,是唯心的。而类型是类和接口这两种概念的编译器实现的映射概念,也是唯物的。类型主要是用来指导编译器的类型检查的谓词,类是创建现实对象的模板,接口是OO设计中的关键概念。这三个概念相互区别(分别位于不同的概念域),又相互联系(都是代表相同的概念的不同概念域的映射)。有了上面的理解,我们看看下面最常见的Java语句:
people a=new man();
这代表了什么?程序员向编译器声明了一个people类型(type)的对象变量a,而对象变量a本身却指向了一个man类(class)的实体(而在编译器中理解是对象变量a指向了一个类型为man的实体)。再让我们回到[Jams 2000],其中句子的根本含义我们可以概括如下:声明一个类或者一个接口都同时向编译器注册了一个新的类型,而此类或者接口以及类型都是共享同样的一个名字。也就是说。编译器所能理解的全部都是类型,而程序员的工作是把现实中的类概念转化为设计中的接口概念,而编译器对应于上两种概念都有直接的支持,那就是一个类声明或者接口声明在编译器的理解来看就是一个类型声明。但是反过来却不一定成立。一个类可以有多个接口(一个类完全有可能实现了设计人员的多个契约条件),同时也就可能有多个类型(因为类型不过是接口这个设计域内的概念在编译器中的实现)。