把乘法变成加法 不要误会,不是用加法重载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 Cursor, class Binder, class RowBuffer> 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游标,自动绑定,并且按行缓冲的数据库访问类时,只需用相应的类实例化模板即可: MyDBAccessor<FastForwardCursor, DynamicBinder, SingleRowBuffer>da; … 如果我们需要一个支持Dynamic游标,字符串绑定(所有类型转换成字符串),快缓冲的数据库访问类,也很方便: MyDBAccessor<DynamicCursor, StringBinder, BulkBuffer>da; … 在GP方式中,我们只需定义8+5+3+1=17个类和模板,即可实现OOP方式中121类定义才能达到的效果。 非常好吧。还不止于此。假设你希望用DynamicCursor游标访问数据库,但是写错了变成了这样: MyDBAccessor<FastForwardCursor, DynamicBinder, SingleRowBuffer>da; … da.MoveFirst();//啊呀! 不要告诉我你不会犯这种低级错误。这种错误每时每刻都在发生,最可能的一种情况就是软件的设计改了,由fast-forward游标改成dynamic游标,而你却忘了修改da的声明。 此 时,代码不会编译通过。因为MyDBAccessor继承自游标管理类,并从游标管理类继承了操纵游标的成员函数。于是,根据fast-forward的 定义:只进不退,所以没有MoveFirst()函数(也不需要,对吧)。因此,MyDBAccessor<FastForwardCursor, DynamicBinder, SingleRowBuffer>也没有这个函数。那么da.MoveFirst()便会引发编译错误。 很 多初学者可能不喜欢这种设计,因为他们非常害怕编译器错误。就好像编译器是他们中学语文老师一样。其实,你应该感谢这个编译错误,它在第一时间帮你消除了 一个潜在的bug。如果我们在FastForwardCursor中加上MoveFirst()这个函数,编译自然没有问题。但在运行时,这句代码肯定会 引发一个运行时错误。运气好的话在你测试的时候,运气不好的话可能会在你客户心情最差的时候发生。这个后果,…,哎呀呀。 另外,即使在你测试的时候发生,你也会被迫用几十上百个单步追查问题的原因,以至于把你周末约会女朋友的心情都搞坏了。 好 了,乘法变加法的把戏变完了。简单地讲,就是你可以利用模板、继承模板参数,以及多继承等技术,将一些基本的要素组合起来,构成一个复杂的,功能完整的 类。用最少的代码作最多的事。这种技术是由C++领域的先锋官Andrei Alexandresu提出来的,称为policy。更详细的内容,可以参考他的《Modren C++ Design》,里面有很详细的讲解和案例。不过得做好心理准备,接受大剂量模板。 |
|
来自: ShangShujie > 《c》