C语言实现封装特性
注:本文原型摘自《架构整洁之道》第5章 面向对象编程,经过作者加工修改
1. 封装特性
封装是面向对象编程的基本思想之一,通过采用封装特性,我们可以把一组相关联的数据和函数圈起来,使圈外面的代码只能看见部分函数,数据则完全不可见。
然而由于C++编译器必须知道每个类实例的大小,因此要求类的成员变量必须在该类的头文件中声明,这样反而违背了封装性。
反而非面向对象的C语言,可以通过一些手法实现完美的封装特性。
2. C实现封装举例
namedpoint.h 代码如下, 头文件对外声明NamedPoint类型的结构体,和4个相关的函数。
注意该头文件中,并未暴露NamedPoint的数据细节,即外部调用方只知道存在NamedPoint这个数据结构,但是无法得知NamedPoint内部数据是如何组织的。
外部模块只能通过头文件提供的4个函数去操作NamedPoint这个数据结构。
// namedpoint.h
#ifndef __NAMEDPOINT_H__
#define __NAMEDPOINT_H__
typedef struct _NamedPoint NamedPoint;
NamedPoint* makeNamedPoint(int x, int y, char* name);
void setName(NamedPoint* p, char* name);
char* getName(NamedPoint* p);
double distance(NamedPoint* from, NamedPoint* to);
#endif
namedpoint.c 文件中实现了NamedPoint结构体的实现细节,和提供给外部调用的函数。
这里NamedPoint结构体的数据完全封装在namedpoint.c文件中,调用NamedPoint的模块是无法得知其内部数据的组织细节。
// namedpoint.c
#include <stdlib.h>
#include <math.h>
#include "namedpoint.h"
struct _NamedPoint
{
double x, y;
char* name;
};
NamedPoint* makeNamedPoint(int x, int y, char* name)
{
NamedPoint* p = (NamedPoint*)malloc(sizeof(NamedPoint));
p->x = x;
p->y = y;
p->name = name;
return p;
}
void setName(NamedPoint* p, char* name)
{
p->name = name;
}
char* getName(NamedPoint* p)
{
return p->name;
}
double distance(NamedPoint* from, NamedPoint* to)
{
double dx = from->x - to->x;
double dy = from->y - to->y;
return sqrt(dx * dx + dy * dy);
}
main.c中提供了调用示例,在main.c中,仅根据namedpoint.h头文件中提供的信息来使用NamedPoint类。
// main.c
#include <stdio.h>
#include <stdlib.h>
#include "namedpoint.h"
int main(void)
{
NamedPoint* orgin = makeNamedPoint(0.0, 0.0, "orgin");
NamedPoint* upperRight = makeNamedPoint(6.0, 8.0, "upperRight");
printf("distance=%f\n", distance(orgin, upperRight));
return 0;
}
调试执行结果如下:
上面的例子中,NamedPoint类在namedpoint.c中实现数据组织和函数实现。在namedpoint.h中,仅提供了数据类型名,和外部接口函数,从外部并无法得知NamedPoint类内部数据的组织方式。main.c中使用NamedPoint类时,也仅通过接口使用,并无法知道其内部数据的组织方式。由此达到了比较理想的“封装”特性。
3. 总结
如上,虽然C并不是面向对象的编程语言,但是仍然以实现隐藏数据细节的方式,实现完美的封装效果。