仿基因编程导论
1. 引论
基于接口是面向对象编程的一个重要的基础。使用基于接口的编程模式,程序可以获得兼容方面的好处。新的程序只要遵循以前的接口定义就可以与以前的应用完全兼容。但这种编程模式,没法应对功能扩展。这篇文章所要介绍的仿基因编程正是为了解决功能扩展这个问题而产生的一种编程模式。
本文所要讲述的是基因编程的基本原理。文中涉及到了一些面向对象和泛型编程中所有的一些概念。如果读者有这两方面的经验很快就会理解和运用仿基因编程技术。
1.1. 为什么要进行仿基因编程?
主要原因有三个:一个是基于接口的编程扩展能力差。 一个是接口用法太复杂。一个是接口有一种天然的排它性。
第一点我想程序员们比我还要清楚,就不讲了。
说到第二点程序员们也许会惊讶,会说:“用法太复杂! 不会吧,不就是建立一个接口,然后再直接调用它的方法不就行了嘛。不复杂呀。”但是想一想,接口也是一些功能块,它的职责也是数据加工处理,要是只暴露数据不是更好吗?而且如果要进行双向通信,客户端做回调函数的设置,这看起来就有点怪,要是把这个也做的象一个一般的数据处理就好了。仿基因编程则能很好的处理这两个问题。在仿基因编程模式下,你的接口可以任意地扩展功能,而且客户端、服务器端的概念也不在有严格的区分了。再也没有设置回调函数这一说了,双向通信是模型的天然特性。
接口的排它性所指的是这样的一个问题,一般我们很难在同一个应用中同时使用同一接口的不同版本或同一接口的不同实现。比如你想同时使用接口Icontrl的1.0版本和2.0版本,再比如你想同时使用A公司和B公司的接口Icontrl。在这两种情况下,如果你不做一些特殊处理的话你将除了能得到一个编译错误(类型重定义)外什么也得不到。仿基因编程模式为解决这个问题提供一个简单、易用的方法。
好了,关于为什么要使用仿基因编程,如果这三个理由还不能说服你的话,我就真的没有必要再说别的了。
1.2. 仿基因编程的基本思想
仿基因编程是在传统的基于接口的基础上接合双向多态和模板技术及动态联编这三项技术而产生的。但它的基本精神却和基于接口的基本精神正好相反。 基于接口的编程是通过对外暴露方法隐藏数据,实现时利用动态多态来完成实际的任务。而仿基因编程却是通过对外暴露数据隐藏方法,实现时利用双向动态多态来完成实际的任务。仿基因编程有一个重要目标,那就是方便的代码扩张。
1.3. 仿基因编程模型与基于接口编程模型的对比
在进行仿基因编程的具体方法之前我们先把仿基因、基于接口这两种编程模式作一个对比,来进一步地了解仿基因编程所要解决的问题。这里有一个两者的模型图。我们将结合这两个图来说明这些问题。见图1-1、图1-2
图1-1
图1-2
图1-2中的一对交互的接口从形状看有点象DNA的双螺旋结构。实际上它的一些生长过程也与遗传和变异很相象。这正是仿基因编程这个名字由来。从这两个模型的对比中我们能发现这样一些问题。
l 仿基因编程中不再有严格意义是的客户端和服务器端的区分了。而基于接口的编程中这两者有明显的区分。
l 仿基因编程中的接口只向外公开数据,再没有其它。而基于接口的编程中接口要向外公开三种类型的成分,方法中、属性、回调机制。
l 仿基因编程中数据交换这一种操作外再没有其它。而接口的使用就要区分三种性质的使用方法。
l 如果要进行功能扩充,在仿基因编程中只要新加一种数据就行了。而且这个新数据可以由接口的使用方进行扩充。而基于接口的编程想要对接口进行功能扩充是不可能的。
从这些对比中我们发现相比基于接口的编程,仿基因编程的用法简单了,但灵活性却极大地提升了。
2. 仿基因编程的实现方法
对所要做的工作进行抽象化是编程的根本。只有从实际要完成的任务中抽象出一个一个的概念,再对这些概念进行进一步的加工整理,进行更高一级的抽象化,最后才能确定怎么样编码来实现实际的工作。在进行仿基因编程的实现方法之前我们先对编程这种工作进行一下抽象化,来抽象出编程工作中最最根本的概念。经过抽象,我们发现所有的编程工作中只有两个概念是必不可少的。一个就是数据,一个就是数据处理的方法。我们就把这两个概念作为什么基因编程的源头,依据这个源头一步步地深入来讲述要进行仿基因编程所必需要处理的几件事情。
2.1. 什么也不做的接口和什么没有的数据。
为了实现前面提到的哪些功能,我们需要一些基本的东西。这就一个什么也没有的数据和一个什么也不做的接口。它们看起来像这个样子。见图2-1。
图2-1
这是我们在画布上的第一笔。来看看这第一笔为我们提供了哪些基础。首先我们有了一个抽象数据类型DataBase,这个数据类型没有任何内容,只有一个纯虚的析构函数。另外我们还有了一个很简单的接口InterfaceBase,这个接口接受DataBase类型的数据的流入和流出。
这时我们有了无限多种可以接受无限多种数据类型的接口了。不是吗?从InterfaceBase派生而来的任意的接口可以接受从DataBase派生而来的任意数据。可是由于子类型向上转型的过程中,子类型的信息丢失了,我们的接口不能鉴别子类型的具体类型了,而是只把它当做DataBase来处理。而DataBase却是一个什么内容也没有的类型,这样我们什么了也做不了。我们需要一个机制来把子类型鉴别出来。
2.2. 动态子类型鉴别
由于子类型在向上转型中类型信息丢失,导致我们上面的模型成了一个什么事也干不了的模型。要是能把这些子类型信息想个办法找回来就好了。要找回这些信息我们首先会想到RTTI。但使用RTTI会让我们的扩展被限制。所以要放弃RTTI的方法。(关于RTTI详细的情况在这里就不说了。)现在要是每一个子类型都能提供一个能力来让接口确定它的类型就好了。要实现这样的一个功能并不难。只要用dynamic_cast进行转换就行了。有了这些我们就可以在不对接口进行任何改动的情况下,对接口进行功能扩充了。而且这个扩充是双向的,可以由接口的提供者扩充,也可以由接口的使用者扩充。
2.3. 数据类型依理分类
可以想象,如果依照上面的方法来做模型,那么数据类型的增长速度会不可思议的快。最终我们将会得到一个数量不可思议多的数据类型集。要管理这个庞大类型集将会非常困难。为此,我们需要对数据类型进行分类,让每一个接口所能处理的数据类型都能与这个接口产生天然的联系。这里有两个天然的分类标准,一个是公司,一个接口类型。首先为自己的公司定义一个这样的辅助类。
class company
{
public:
static const string& Name_s()
{
static string name = "company name";
return name;
}
virtual const string& Name_o()
{
return Name_s();
}
};
为了让分类工作自动化我们定义这样一个模板
templateclass INTERFACE, class COMPANY >