之前在参加宋宝华老师的培训时,宋老师经常说写代码要做到高内聚低耦合,当时并不太理解其意义,只是感觉这句话是正确的,但是不知道具体如何操作,后来代码写的看的多了,才初窥门径,下面就是自己的一点经验总结。
一 高内聚低耦合含义
写代码时,一般会划分模块,模块内,要做到高内聚,而模块之间则要低耦合,这样可使代码的模块化更好,提高了可重用性和可移植性。
道理很好理解。
二 举例
1. 使用头文件
假设我们有个模块叫func,其代码全部放在func目录下,其结构如下,
这个模块对外接口都放在func.h里,而具体实现代码则是放在src目录下的文件里。func模块的功能就是提供了一个func函数,func.h如下,
#ifndef _FUNC_H_
#define _FUNC_H_
int func(void);
#endif
别的代码要使用这个func模块,只要直接包含这个头文件就可以了,而不用去关心模块内部的实现细节,也不用去包含这些实现文件。
func.c内容如下,
#include "aa.h"
#include "bb.h"
int func(void)
{
return 50 + helper_aa(50) + helper_bb(50);
}
aa.h和aa.c内容如下,
// aa.h
#ifndef _AA_H_
#define _AA_H_
int helper_aa(int data);
#endif
// aa.c
int helper_aa(int data)
{
return data * 10;
}
bb.h和bb.c的内容如下,
// bb.h
#ifndef _BB_H_
#define _BB_H_
int helper_bb(int data);
#endif
// bb.c
int helper_bb(int data)
{
return data * 20;
}
假如我们现在有个main.c,里面要使用func模块的功能,main.c的内容如下,
// main.c
#include <stdio.h>
#include "func.h"
int main(void)
{
int data = func();
printf("data from func: %d\n", data);
return 0;
}
这样对于main()函数来说,它能看到的仅仅是func.h头文件里的函数声明,而func模块内部的实现细节它一点都不需要关心。
这样就做到了高内聚和低耦合。高内聚是指func模块的实现都在模块内部,外部看不到也不需要关心,当func模块的实现代码需要修改时,调用者代码不需要重新编译。低耦合是指其它模块或者函数需要使用func模块功能时,只用包含func.h这个头文件就可以了。
同理,也可以把main函数想象成别的模块使用func模块提供的功能。
2. 使用前置声明
使用前置声明也可以实现高内聚低耦合,甚至不用包含接口头文件,下面就来看下如何操作的。
假如我们有个叫func的模块,其结构体如下,
func.h内容如下,
#ifndef _FUNC_H_
#define _FUNC_H_
struct FuncData {
int data1;
int data2;
};
// 分配一个struct FuncData变量
struct FuncData * newFuncData(void);
// 增加struct FuncData里的成员变量值
void increaseFuncData(struct FuncData *ptr);
// 打印struct FuncData里的成员变量值
void printCurrentValue(struct FuncData *ptr);
#endif
func.c内容如下,
#include <stdlib.h>
#include <stdio.h>
#include "func.h"
struct FuncData * newFuncData(void)
{
return new calloc(1, sizeof(struct FuncData));
}
void increaseFuncData(struct FuncData *ptr)
{
ptr->data1 += 10;
ptr->data2 += 20;
}
void printCurrentValue(struct FuncData *ptr)
{
printf("data1 of FuncData: %d\n", ptr->data1);
printf("data2 of FuncData: %d\n", ptr->data2);
}
这时我们有个main.c,会调用这个func模块里的功能,其代码如下,
#include <stdio.h>
struct FuncData; // 前置声明
struct FuncData * newFuncData(void);
void increaseFuncData(struct FuncData *ptr);
void printCurrentValue(struct FuncData *ptr);
int main(void)
{
struct FuncData *ptr = newFuncData();
if (ptr == NULL)
{
printf("Error in newFuncData()\n");
return -1;
}
increaseFuncData(ptr);
printCurrentValue(ptr);
return 0;
}
main.c里没有包含func.h,而是使用了前置声明,对于struct FuncData,虽然使用了前置声明,但是其内部成员变量在main.c里是不知道的,术语叫不完整类型,所以只能使用指针,其相关操作函数传递的参数或返回值都是struct FuncData指针,这样才可以让前置声明运行OK。
前置声明可以让我们根据需要挑选需要的模块功能,而不用包含模块的接口头文件,这样可以减少编译量。而且和头文件一样,模块的实现代码发生变化,只要接口不变就不用重新编译调用者的程序。
三 总结
本文主要使用简单代码讲述如何实现代码模块的高内聚和低耦合,这样使得编写的代码结构更加清晰,且更符合软件工程的要求。如果代码量比较小,可能感觉不到带来的好处,但是当工程变得复杂,其优点就会充分体现出来了。
如果有写的不对的地方,希望能留言指正,谢谢阅读。