这样,最简单的考虑,我就是返回这个链表的头给使用者就可以了。StringDescriptor中有所有的信息,包括U盘的序列号,那么我应该这样写我的头文件:
#ifndef …如果我以上设计了这个模块,那么对使用者来说,将是一个巨大的困扰。首先,这违反了前面说的原则2.在头文件中包含了另外一个头文件<usbiodef.h>.此外,这个头文件是DDK的头文件。但是使用者只想获得U盘序列号,并不曾想,自己必须改变VC设置,去包含DDK的头文件。此外DDK的头文件和SDK的头文件同时使用,常常出现版本冲突之类的问题,难以配置。但是实际上完全没有必要的。此外,使用者还必须学会如何操作USB_STRING_DESCRIPTOR。而且使用者必须自己操作链表。这又带来更多的问题:使用者能否安全的操作链表呢?操作过程中是否要加锁呢?
#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
原则3. 头文件只提供给使用者必要的东西,绝不把任何多余的东西包含进去。
下面做一个简单的修改。实际上,我们返回的依然是链表。但是,我们却不让使用者看见链表,以及DDK特有数据结构的存在。
#ifndef …这里用了一个void *代替返回链表。用umsGetNextDiskID来遍历链表。用户只能看见一个const wchar_t*返回的U盘序列号。不需要包含其他任何头文件,也不需要担忧链表使用的安全性。这是一个符合原则3的设计。
#define …
void *umsGetAllUDisk( );
const wchar_t* umsGetNextDiskID(void * umsDescHandle);
void umsFreeAllUDisk(void * umsDescHandle);
#endif
三 解除依赖
依赖关系是往往是代码复用最大的羁绊。下面再举一个实际中的例子。我们在开发驱动的过程中,编写了一个模块,这个模块可以在驱动中把计算机名转化为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)当然,我在初始化WNS的时候,要根据客户的要求,指定这个函数指针。比如说在Infocage项目中,客户要求使用IcLog().
sMyLogFunction(…);
void wnsInitialize(WNS_LOG_OUTPUT_F log_function)在另一个项目中我可以使用另外的实际接口。
{
…
sMyLogFunction = log_function;
}
如果函数原型不同,我总是可以定义一个简单的中间函数来满足两边的接口匹配。
同样,内存分配函数也是如此。
依赖关系是可以被解除的。关键只在于解除的花费与所得的比例。小心的设计编码,微妙的改变代码架构,往往可以巧妙解除依赖关系链,使代码变得可重用。