在之前menu版本的基础上,给Linktable增加Callback方式的接口。接口只描述模块做什么,但不会包含怎么做。完成接口所做出的承诺的代码被称为实现。通过将模块的接口和实现分离,我们可以对系统的其它部分隐藏实现的复杂度。
基于lab5.2的代码分析:给Linktable增加Callback方式的接口,需要两个函数接口,一个是call-in方式函数,如SearchLinkTableNode函数,其中有一个函数作为参数,这个作为参数的函数就是callback函数,如代码中Conditon函数。打开llinktable.h,可以看到SearchLinkTableNode的定义:
/*
* Search a LinkTableNode from LinkTable
* int Conditon(tLinkTableNode * pNode, void * args);
*/
tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable, int Conditon(tLinkTableNode * pNode, void * args), void * args);
在lab5.2中,call-in方式的函数接口SearchLinkTableNode增加了一个参数args,callback函数Conditon也增加了一个参数args。增加此参数的原因是:
根据接口与耦合度之间的关系:
对于软件模块之间的耦合度,前文中提到,耦合度是指软件模块之间的依赖程度,一般可以分为紧密耦合(Tightly Coupled)、松散耦合(Loosely Coupled)和无耦合(Uncoupled)。一般在软件设计中我们追求松散耦合。
更细致地对耦合度进一步划分的话,耦合度依次递增可以分为无耦合、数据耦合、标记耦合、控制耦合、公共耦合和内容耦合。这些耦合度划分的依据就是接口的定义方式,我们接下来重点分析一下公共耦合、数据耦合和标记耦合。•公共耦合
•当软件模块之间共享数据区或变量名的软件模块之间即是公共耦合,显然两个软件模块之间的接口定义不是通过显式的调用方式,而是隐式的共享了共享了数据区或变量名。
•数据耦合
•在软件模块之间仅通过显式的调用传递基本数据类型即为数据耦合。
•标记耦合
•在软件模块之间仅通过显式的调用传递复杂的数据结构(结构化数据)即为标记耦合,这时数据的结构成为调用双方软件模块隐含的规格约定,因此耦合度要比数据耦合高。但相比公共耦合没有经过显式的调用传递数据的方式耦合度要低。
cmd是全局变量,共享cmd这一变量名即公共耦合,为了追求松散耦合,我们增加一个args参数,代替共享cmd这一方式,降低耦合度。之所以要转成void类型,就是为了更通用,是因为我们不想让底层的linktable模块知道上层用的数据类型。这样同时也不影响找节点的功能。
我们还通过将linktable.h中不是在接口调用时必须内容转移到linktable.c中,这样可以有效地隐藏软件模块内部的实现细节,为外部调用接口的开发者提供更加简洁的接口信息,同时也减少外部调用接口的开发者有意或无意的破坏软件模块的内部数据。
在linktable.c中找到这个函数的实现代码:
tLinkTableNode * SearchLinkTableNode(tLinkTable *pLinkTable,
int Condition(tLinkTableNode * pNode, void * args),
void * args)
{
if(pLinkTable == NULL || Condition == NULL)
{
return NULL;
}
tLinkTableNode * pNode = pLinkTable->pHead;
while(pNode != NULL)
{
if(Condition(pNode, args) == SUCCESS)
{
return pNode;
}
pNode = pNode->pNext;
}
return NULL;
}
在menu.c中查看调用SearchLinkTableNode的地方FindCmd函数:
/* find a cmd in the linklist and return the datanode pointer */
tDataNode* FindCmd(tLinkTable * head, char * cmd)
{
return (tDataNode*)SearchLinkTableNode(head, SearchCondition, (void*)cmd);
}
可以看到这个函数调用这一接口时Condition函数传递的是SearchCondition,SearchCondition函数返回值是tLinkTableNode *类型,而FindCmd函数调用时把返回值转成了tDataNode*类型。
这里没看懂,看了下这位同学的博客看懂了:感谢感谢Linktable增加Callback方式的接口_青衫客36的博客-CSDN博客
可以看到在menu.c中的主函数:先是InitMenuData(&head)初始化了head,再在FindCmd中传递head参数:
tLinkTable * head = NULL;
int main()
{
InitMenuData(&head);
/* cmd line begins */
while(1)
{
char cmd[CMD_MAX_LEN];
printf("Input a cmd number > ");
scanf("%s", cmd);
tDataNode *p = FindCmd(head, cmd);
在InitMenuData(&head)中:head被初始化为创建了tLinkTable *类型
int InitMenuData(tLinkTable ** ppLinktable)
{
*ppLinktable = CreateLinkTable();
tDataNode* pNode = (tDataNode*)malloc(sizeof(tDataNode));
pNode->cmd = "help";
pNode->desc = "Menu List:";
pNode->handler = Help;
把用到的关键代码部分合起来放在这里:(linktable.h和linktableInternal.h中)
/*
* LinkTable Node Type
*/
typedef struct LinkTableNode tLinkTableNode;
/*
* LinkTable Type
*/
typedef struct LinkTable tLinkTable;
/*
* LinkTable Node Type
*/
struct LinkTableNode
{
struct LinkTableNode * pNext;
};
/*
* LinkTable Type
*/
struct LinkTable
{
struct LinkTableNode *pHead;
struct LinkTableNode *pTail;
int SumOfNode;
pthread_mutex_t mutex;
};
/*
* Create a LinkTable
*/
tLinkTable * CreateLinkTable()
/*
* Add a LinkTableNode to LinkTable
*/
int AddLinkTableNode(tLinkTable *pLinkTable, tLinkTableNode * pNode)
(menu.c中的)
typedef struct DataNode
{
tLinkTableNode head;
char* cmd;
char* desc;
int (*handler)();
} tDataNode;
可以分析得知head是通过CreateLinkTable创建了一个tLinkTable*即链表头指针,再调用AddLinkTableNode往链表中插入tLinkTable *类型的数据,而InitMenuData函数在这个链表中插入的都是tDataNode*,这里是多态的实现,让“父类”(父结构体tLinkTableNode)指针指向“子类”(子结构体tDataNode)的对象,由于类型不匹配所以要进行强制类型转换。即通过强制指针转换来达到访问不同对象的目的,将指针的类型转换以此来执行不同的对象。这里是C语言实现多态,C中没有Class但是又类似Class的struct,这里就正好利用struct和指针实现了多态。多态是面向对象内容中普遍熟悉的知识就不多做分析了。
因此在初始化后面的FindCmd函数里也是通过强制类型转换可以直接得到“子类”的数据tDataNode*。这样实现多态这样可以有效地隐藏软件模块内部的实现细节,为外部调用接口的开发者提供更加简洁的接口信息。