为什么要用C来模拟面向对象的机制,在实际的工作中我们往往在感慨一些面向对象的经典设计模式由于C语言的限制无法使用,其实通过简单的模拟面向对象的行为,在C语言中也可以使用这些模式。
1:类的构建
类描述了所创建的对象共同的属性和方法。我们在一个源文件中通过把数据和操作进行适当的组织来完成类的模拟。
/*类的数据*/
typedef struct SQUARE_S SQUARE_T; struct SQUARE_S { void (*draw)(void*); int sideLen; };
/*类的方法*/
static void draw(void* pObj) { SQUARE_T* pSqr = (SQUARE_T*)pObj; printf("Draw Square len is %d\n",pSqr->sideLen); }
如上所示,一个正方形的类我们用一个结构体SQUARE_T来表示正方形的属性,draw是其中的一个方法。
2:类的封装性
类的封装一般要求对细节的隐藏并且提供指定的方法供调用者使用,在SQUARE这个类中,sideLen是图形的细节,只需要提供一个draw接口给调用者。因此在提供给外部调用的接口头文件中构建如下的接口。
typedef struct SHAPE_S SHAPE; struct SHAPE_S { void (*draw)(void*); };
通过定义不同的数据结构来达到数据隐藏的目的,如下图所示,对外接口中只能看到draw,内部实现中可以看到draw和sideLen。
3:多态的模拟
多态无疑是面向对象语言的很重要的一个机制,很多面向对象的设计模式都是以多态为基础,C语言并不支持多态,导致很多设计模式都无法直接使用。
一个典型的多态例子,通过声明一个SHAPE接口,根据实例化对象类型的不同,pShape在运行时动态的表现不同的行为。
SHAPE* pShape = NULL; //一个形状接口 pShape = (SHAPE*)Ins(SQUARE,2); //实例化为一个正方形 pShape->draw(pShape); //pShape表现为正方形的行为
多态机制的实现依赖函数指针,在每个类的构造函数中把相关接口用具体的函数地址填充,这样在实例化一个对象的时候我们才绑定了其具体的操作,也就是所谓的动态绑定。
/*每个类的构造函数*/ static void* Constructor(void* pObj,va_list* pData) { SQUARE_T* pSquare = (SQUARE_T*)pObj; pSquare->draw = draw; //具体行为的填充 pSquare->sideLen = va_arg(*pData,int); return pObj; }
4:对象的创建
有了类,我们需要实例化为可以运行的对象,实例化主要的工作是分配内存、动态绑定、数据初始化等工作。
void* Ins(const void* pClass,...) { CLASS* pCls = NULL; void* pObj = NULL; va_list vaList = NULL; pCls = (CLASS* )pClass; pObj = malloc(pCls->classSize); memset(pObj,0,pCls->classSize); va_start(vaList,pClass); pObj = pCls->Constructor(pObj,&vaList); return pObj;
}