技术开发 频道

C/C++项目中的代码复用和管理


    这样,最简单的考虑,我就是返回这个链表的头给使用者就可以了。StringDescriptor中有所有的信息,包括U盘的序列号,那么我应该这样写我的头文件:

#ifndef … 
#define …
#include <usbiodef.h>
typedef struct _STRING_DESCRIPTOR_NODE
{
struct _STRING_DESCRIPTOR_NODE * Next;
UCHAR DescriptorIndex;
USHORT LanguageID;
USB_STRING_DESCRIPTOR StringDescriptor[0];
} STRING_DESCRIPTOR_NODE, *PSTRING_DESCRIPTOR_NODE;

PSTRING_DESCRIPTOR_NODE umsGetAllDisks();
#endif
    如果我以上设计了这个模块,那么对使用者来说,将是一个巨大的困扰。首先,这违反了前面说的原则2.在头文件中包含了另外一个头文件<usbiodef.h>.此外,这个头文件是DDK的头文件。但是使用者只想获得U盘序列号,并不曾想,自己必须改变VC设置,去包含DDK的头文件。此外DDK的头文件和SDK的头文件同时使用,常常出现版本冲突之类的问题,难以配置。但是实际上完全没有必要的。此外,使用者还必须学会如何操作USB_STRING_DESCRIPTOR。而且使用者必须自己操作链表。这又带来更多的问题:使用者能否安全的操作链表呢?操作过程中是否要加锁呢?

    原则3. 头文件只提供给使用者必要的东西,绝不把任何多余的东西包含进去。

   下面做一个简单的修改。实际上,我们返回的依然是链表。但是,我们却不让使用者看见链表,以及DDK特有数据结构的存在。

#ifndef …
#define …
void *umsGetAllUDisk( );
const wchar_t* umsGetNextDiskID(void * umsDescHandle);
void umsFreeAllUDisk(void * umsDescHandle);
#endif
   这里用了一个void *代替返回链表。用umsGetNextDiskID来遍历链表。用户只能看见一个const wchar_t*返回的U盘序列号。不需要包含其他任何头文件,也不需要担忧链表使用的安全性。这是一个符合原则3的设计。 

三 解除依赖

    依赖关系是往往是代码复用最大的羁绊。下面再举一个实际中的例子。我们在开发驱动的过程中,编写了一个模块,这个模块可以在驱动中把计算机名转化为ip地址。我把这个模块命名为WNS,编译出一个WNS.lib的静态库给别人使用。

    但是我们遇到了第一个问题。在Infocage项目中,客户要求所有的组件在异常情况都要出Log,必须调用规定的IcLog函数.此外,还有所有的组件都要使用规定的函数IcMemAllocate和IcMemFree来分配和释放内存。

   这样一来,我的WNS中也必须调用IcLog来出Log,同时必须使用IcMemAllocate来分配内存。

   在另一个工程,假设名字叫Capsuit,则完全不同。他们要求所有的组件都要用CsLog模块来出Log,并要用CsMem模块来分配内存。

   那么WNS如何适应呢?此时很多人就认为,独立出这样的模块给两个工程使用,本来是可行的。但是由于客户的需求,所以实际不能做到。

   但是这个想法是错误的。关键在于,我们没有很好的理解“解除依赖”的方法。

   WNS可以使用IcLog模块来输出 Log.但这并不意味者,WNS必须依赖Log.我们假设上面的说法成立,那么WNS必须依赖IcLog.如果IcLog的Log实质上是写入Oracle数据库的,那么你会发现所有要出Log的组件都依赖于Oracle,那么独立模块根本就是不可能存在的。

    实际上,WNS可以不依赖于IcLog.在C++中,很容易用虚函数实现这一点。在C中,也很容易设置回调函数来实现。

    WNS要出Log,我们可以假设依赖于如下一个Log函数:
void wnsLogOutput(const wchar_t *format,…);
    但是这个函数实际并不存在,我定义一个函数类型:
typedef void (*WNS_LOG_OUTPUT_F)(const wchar_t *format, …);
    然后定义一个函数指针:
static WNS_LOG_OUTPUT_F sMyLogFunction = NULL;
   之后我在WNS中,我都只用这个函数指针来输出 Log:
if(sMyLogFunction != NULL) 
sMyLogFunction(…);
   当然,我在初始化WNS的时候,要根据客户的要求,指定这个函数指针。比如说在Infocage项目中,客户要求使用IcLog().

void wnsInitialize(WNS_LOG_OUTPUT_F log_function) 
{

sMyLogFunction = log_function;
}
    在另一个项目中我可以使用另外的实际接口。

    如果函数原型不同,我总是可以定义一个简单的中间函数来满足两边的接口匹配。

    同样,内存分配函数也是如此。

    依赖关系是可以被解除的。关键只在于解除的花费与所得的比例。小心的设计编码,微妙的改变代码架构,往往可以巧妙解除依赖关系链,使代码变得可重用。




0
相关文章