GP(Generic Programming,泛型编程)号称编程思想的又一次革命。但是,在论述GP的资料中,一般都是以C++语言为基础来讨论。那么,GP是否可以在其它的编程语言中实现呢?这是一直在思考的一个问题,因为水平有限和资料匮乏,收获甚微。现将一些不成熟的想法整理出来,请方家不吝指教。
以Delphi为例(Java的情况与此类似,可参照),讨论GP的另一种实现思路。代码是随手写出的,未经验证。
根据的理解,实现GP的关键之处,在于实现ADT(Abstract Data Type,抽象数据类型)。只有实现了ADT,才能够将具体的数据类型与通用的算法分离开来。
在C++中,ADT的存储是通过模板来实现的。举一个最简单的栈的例子(没有给出实现部分):
template class Type class Stack{
public:
void Push(const Type &item);
Type Pop;
...
}
栈的应用:
Stack int s;
int data;
data = 1;
s.Push(data); file://入栈
int out;
out = s.Pop; file://出栈
通过建立一个int类型的Stack对象,实现了对int类型数据的存储。
但是,在Delphi/Java中,并没有模板这种机制。那么如何实现ADT呢?与C++不同的是,Delphi/Java的类继承体系是单根结构的,也就是说,在Delphi中,所有的class都通过编译器强制而保证成为TObject的子类(Java中是Object)。这个TObject可以看成是一切类的最高层次的抽象。那么,是否可以认为Delphi/Java中已经先天地提供了对ADT的支持呢?
试着用这种思路建立一个栈:
TStack = class
public
procedure Push(item:TObject);
function Pop:TObject;
...
end;
这个TStack类针对TObject类型的对象进行操作。但是,这个类还不能立即应用,因为在Delphi中,简单数据类型并不是对象。所以必须再建立一个自定义的数据类型。下面建立了只有一个integer成员的自定义数据类型:
TADT = class
public
data:integer;
end;
再来看栈的应用:
var
stack:TStack;
adt1,adt2:TADT;
begin
stack := TStack.create;
adt1 := TADT.create;
stack.Push(adt1); file://入栈
adt2 := stack.Pop as TADT; file://出栈
stack.free;
这样就完成了对ADT对象的存储。必须注意到,在入栈时,是将adt对象直接入栈的,因为TStack类是对TObject进行操作,由于Delphi的单根结构,可以将任何类型的对象赋给TObject类型的变量。但是,出栈时,返回的也是一个TObject的变量,因此必须用as完成一次向下映射。同样由于单根结构,Delphi/Java提供了对RTTI的强大支持,因此这种向下映射是轻而易举的事情。但是,毫无疑问,RTTI在效率上有所损失。