强大的C ——把乘法变成加法

来源:百度文库 编辑:神马文学网 时间:2024/04/29 10:48:15
把乘法变成加法
不要误会,不是用加法重载operator*。(做这种事情的程序员应该立刻开除)。或者任何跟计算有关的事。这里要讲的是另外一个故事。
当你看我这篇帖子的时候,是否想过你的计算机是如何构成的?内存、主板、硬盘、cpu、显卡、显示器、光驱、键盘、鼠标等等。没错,你肯定很熟悉了。那么,你是否想过电脑厂商为了生产不同的配置的计算机,准备了多少配件吗?不好意思,我也不清楚。不过没关系,我们可以假设。假设内存规格有256、512、1G、2G四种规格(不考虑牌号,后面也一样);硬盘规格有80G、100G、120G、160G和200G五种规格;显卡有三种(假设一下,我搞不清现在有多少种显卡);cpu有五款;显示器有4种;光驱有5种;鼠标键盘就不考虑了。
那么我们总共可以得到多少种配置呢?很简单,4*5*3*5*4*5=6000种!当然没有哪个厂商会推出6000款型号,只是假设一下。那么总共有多少配件呢?4+5+3+5+4+5=26种。也就是厂商只需管理26种配件,便可以制造出6000个机型。
现在让我们再假设一下,当初IBM发明PC的时候,一时糊涂,没有把PC的各个组成部分组件化,所有的组成部分都是焊在一块电路板上的,包括显示器。那么如果一个厂商想获得这6000种配置的电脑,那么他们就必须直接生产,并且管理6000种不同的组件(电脑)。
这就是差别,组件化vs非组件化:26对6000。
好,现在回到我们熟悉的软件(开发)上来。我们在软件开发是通常也面临计算机厂商同样的问题:产品多样性的问题。即便是同一种软件,在不同的客户那里通常会有不同要求。为每一个客户开发不同的软件,明显是非常低效的。(不幸的是,这种愚蠢的行为,在业界几乎成了惯例)。顺便说明一下,这里的客户是指用你软件的人。如果你开发的是库,那么客户就是使用你的库的人。而本文主要针对的是库开发这种情况。
假设,我们要开发一个数据库访问的包装库。为其它程序员提供方便快捷地访问数据库的能力,使他们免于和难缠的ODBC或OleDB打交道。但是,前提是我们的包装库不能像ADO.net那样折损开发人员的访问能力。
基于这种前提,我们需要考虑数据库访问的几个基本要素。我归纳了一下,大概可以包括这么几个:游标、数据绑定、数据缓冲。为了简化,其他细枝末节暂不考虑。同时,只考虑结果集处理部分。下面,我们将考察两种不同的实现方式:OOP和GP。
先看OOP方式。OOP方式利用多态和后期绑定提供了扩展能力和一致的接口。代码大概会是这样:
class MyDBAccessor
{

virtual bool MoveNext();
virtual bool MovePre();
virtual bool MoveFirst();
virtual bool MoveLast();
virtual bool GetData(const string& field, void** data);
virtual bool GetData(int field, void** data);
virtual bool SetData(const string& field, void* data) {…}
virtual bool SetData(int field, void* data) {…}
virtual void BindColumn(const string& field, DBType type);
virtual void BindColumn(int field, DBType type);
private:
virtual void DefaultBind()=0;

};
因为这里只关心程序的结构,所以只给出声明,略去定义。MyDBAccessor定义了一个基本的框架,对于不同的特性支持,比如不同的游标等等,通过在继承类中重载相应的虚函数实现:
class MyDBFFAccessor//Fast-forward游标类型
: public MyDBAccessor
{

virtual bool MoveNext(){…}
virtual bool MovePre(){…}
virtual bool MoveFirst(){…}
virtual bool MoveLast(){…}

};
那么,当我们需要一个支持Fast-forward游标,自动绑定,并且按行缓冲的数据库访问类时,我们定义了如下的类:
class MyDB_FF_Dyn_Row
: public MyDBAccessor
{

virtual bool MoveNext(){…}
virtual bool MovePre(){…}
virtual bool MoveFirst(){…}
virtual bool MoveLast(){…}
virtual bool GetData(const string& field, void** data) {…}
virtual bool GetData(int field, void** data) {…}
virtual bool SetData(const string& field, void* data) {…}
virtual bool SetData(int field, void* data) {…}
private:
virtual void DefaultBind(){…}

};
如果我们需要一个支持Dynamic游标,字符串绑定(所有类型转换成字符串),块缓冲的数据库访问类,那么就再定义一个继承类。
问题来了,游标类型至少有8种,假设默认绑定方式有5种(自动、宽/窄字符串绑定、xml绑定、手工绑定),数据缓冲方式有3种(行缓冲、块缓冲、数组缓冲)。
那么我们得定义多少个继承类呢?8*5*3+1=121。Mission Impossible,除非你有ms那样的资源。
现在,我们来看看GP(范型编程)方式会不会好一些。我们先定义一个类模板:
template
class MyDBAccessor
: public Cursor, public Binder, public RowBuffer
{

};
应该看出来了吧,模板MyDBAccessor继承自模板类型参数Cursor、Binder、RowBuffer。而这三个模板参数分别对应了游标管理类、绑定类和行缓冲类。根据前面的假设,我们定义了8种游标管理类:
class FastForwardCursor
{
public:
bool MoveNext();
bool MoveLast();
bool GetData(const string& field, void** data);
bool GetData(int field, void** data);
bool SetData(const string& field, void* data) {…}
bool SetData(int field, void* data) {…}
};
class FastForwardReadOnlyCursor
{
public:
bool MoveNext();
bool MoveLast();
bool GetData(const string& field, void** data);
bool GetData(int field, void** data);
};

class DynamicCursor
{
public;
bool MoveNext();
bool MovePre();
bool MoveLast();
bool MoveFirst();
bool GetData(const string& field, void** data);
bool GetData(int field, void** data);
bool SetData(const string& field, void* data) {…}
bool SetData(int field, void* data) {…}
};
细心的人会发现这些游标管理类的接口(成员声明)都不一样,一会儿会告诉你为什么。
其他的绑定类和数据缓冲类都依次定义。当我们需要一个支持Fast-forward游标,自动绑定,并且按行缓冲的数据库访问类时,只需用相应的类实例化模板即可:
MyDBAccessorda;

如果我们需要一个支持Dynamic游标,字符串绑定(所有类型转换成字符串),快缓冲的数据库访问类,也很方便:
MyDBAccessorda;

在GP方式中,我们只需定义8+5+3+1=17个类和模板,即可实现OOP方式中121类定义才能达到的效果。
非常好吧。还不止于此。假设你希望用DynamicCursor游标访问数据库,但是写错了变成了这样:
MyDBAccessorda;

da.MoveFirst();//啊呀!
不要告诉我你不会犯这种低级错误。这种错误每时每刻都在发生,最可能的一种情况就是软件的设计改了,由fast-forward游标改成dynamic游标,而你却忘了修改da的声明。
此时,代码不会编译通过。因为MyDBAccessor继承自游标管理类,并从游标管理类继承了操纵游标的成员函数。于是,根据fast-forward的定义:只进不退,所以没有MoveFirst()函数(也不需要,对吧)。因此,MyDBAccessor也没有这个函数。那么da.MoveFirst()便会引发编译错误。
很多初学者可能不喜欢这种设计,因为他们非常害怕编译器错误。就好像编译器是他们中学语文老师一样。其实,你应该感谢这个编译错误,它在第一时间帮你消除了一个潜在的bug。如果我们在FastForwardCursor中加上MoveFirst()这个函数,编译自然没有问题。但在运行时,这句代码肯定会引发一个运行时错误。运气好的话在你测试的时候,运气不好的话可能会在你客户心情最差的时候发生。这个后果,…,哎呀呀。
另外,即使在你测试的时候发生,你也会被迫用几十上百个单步追查问题的原因,以至于把你周末约会女朋友的心情都搞坏了。
好了,乘法变加法的把戏变完了。简单地讲,就是你可以利用模板、继承模板参数,以及多继承等技术,将一些基本的要素组合起来,构成一个复杂的,功能完整的类。用最少的代码作最多的事。这种技术是由C++领域的先锋官AndreiAlexandresu提出来的,称为policy。更详细的内容,可以参考他的《Modren C++Design》,里面有很详细的讲解和案例。不过得做好心理准备,接受大剂量模板。