不管是接入网还是传输网产品,控制平面代码,上层需要接收网管NMS管理配置API,以及协议相关管理配置;底层需要操作数据平面的API,而且上层可以会修改接口API,数据平面可能会更改芯片方案。
这就涉及到代码模块化的设计,控制平面代码的模块化,需要做到模块独立(一个人维护一个或者N个模块,利于分工)即使芯片方案修改,也只需要更改API接口即可
网管NMS是Java编码的,Java是面向对象设计语言,在界面上的操作的思想,可以完全理解到控制平面的C实现上,和Java的实现一一对象
Java和C++的类,继承,构造函数,析构函数,虚函数,多态,Factory Method工厂方法这些面向对象的特性,C也可以很好的实现,下面结合在项目过程中的个人爱好,来说明用C实现面向对象编码
例子采用person基类,student和teacher子类来说明
animal是和person平行的基类,可能有N多个这样的基类
模块可能有用很多的Module,Person是其中一个,module_person_***是顶层API下的多态接口,PERSON_OBJECT是Person模块的全局结构
(animal对应就是module_animal_***,ANIMAL_OBJECT就是Animal的全局结构,实际中的全局数据就放这里)
(PERSON_OBJECT/ANIMAL_OBJECT类似于log4c中的log4c_category_factory/log4c_appender_factory/log4c_layout_factory,person_object_t和animal_object_t类似于sd_factory_t,这里没有做factory的管理,没有采用factory工厂注册机制,是为了模块做适当的分离)
object_factory_ops_t是object的工厂,fac_new,fac_delete,fac_print工厂的实现基本接口(factory_ops),module_**_**只和fac_**交互
(对应的Person Object就是person_factory_ops,person_new(person_fac_new),person_delete,person_print
Animal Object就是animal_factory_ops,animal_new,animal_delete,animal_print)
Person是一个Object,Person基类有Student和Teacher两个子类
person_class_t是基类,person_descriptor_t是对象描述符,包含ctor(constructor)构造函数,dtor(desconstructor)析构函数,以及desc_modify和desc_dosomthing等等虚函数,
1. list.h
list.h是linux提供的一个双向链表头文件,位于include/linux/list.h,在数据量比较大的时候,采用链表是个不错的方法,最常用的宏定义是
#define INIT_LIST_HEAD(ptr) do { \
(ptr)->next = (ptr); (ptr)->prev = (ptr); \
} while (0)
#define list_entry(ptr, type, member) \
((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); \
pos = pos->next)
list_entry是典型的多态应用,type就是struct结构体类型
typedef struct object_factory_ops_s
{
/* dont use poly, only singleton */
void *(*fac_new)();
void (*fac_delete)();
void (*fac_print)();
}object_factory_ops_t;
typedef struct person_object_s
{
struct list_head person_list_head;
object_factory_ops_t *factory_ops;
}person_object_t;
person_object_t PERSON_OBJECT;
void person_init()
{
INIT_LIST_HEAD(&(PERSON_OBJECT.person_list_head));
PERSON_OBJECT.factory_ops = &person_factory_ops;
}
上面是对全局的person模块PERSON_OBJECT成员初始化,这里只是简单的挂接了函数指针
list_for_each(pos, &(PERSON_OBJECT.person_list_head))
{
pthis = list_entry(pos, person_class_t, person_list);
(PERSON_OBJECT.factory_ops->fac_print)(pthis);
person_cnt++;
}
上面是object_**_**代码中的一部分,主要功能就是遍历PERSON_OBJECT.person_list_head这个链表,得到基类(实际得到的只是指针,看起来是基类,内存同时指向的是一个subclass),并进行打印
2. 类/封装/Factory Method工厂方法
person_class_t是基类,第一个成员desc是对象描述符(必须作为首成员),在person中就存在两种子类,Student和Teacher,他们的构造函数,析构函数,虚函数都不同,这里利用person_type来区分,采用数组就可以管理(如果子类非常的多,也可以用desc_list链表管理,但是实际中,是不太可能的,毕竟子类是已知的,所以没有必要采用链表管理的);
person_type用来区分子类;
name和age是person的基本属性(teacher/student共有的);
person_list是用来记录本结构体的链,每一个结构的链都会添加到PERSON_OBJECT.person_list_head中,用它来管理数据信息
typedef struct person_descriptor_s
{
char *person_type;
//struct list_head desc_list;
int size; /* size of subclass */
void *(*desc_ctor)();
void *(*desc_dtor)();
int (*desc_modify)();
int (*desc_dosomthing)();
}person_descriptor_t;
typedef struct person_class_s
{
person_descriptor_t *desc; /* must be first */
char person_type[32];
char name[32];
int age;
struct list_head person_list;
}person_class_t;
3. 继承
在上面封装中的对象描述符中的size是子对象的结构体大小,也就是说内存中有N份person_class_t的记录,实际上大小根据person_type的不同而不同
下面结构就是子类继承了基类person的结构,base必须做为结构的首成员
typedef struct person_stu_s
{
person_class_t base; /* must be first */
int student_id;
int score;
}person_stu_t;
typedef struct person_teach_s
{
person_class_t base; /* must be first */
int salary;
int bonus;
char *post;
}person_teach_t;
下面是子类的构造函数和析构函数以及虚函数组成的结构
const person_descriptor_t person_stu_desc =
{
"student",
//{0, 0},
sizeof(person_stu_t),
person_stu_ctor,
person_stu_dtor,
person_stu_modify,
person_stu_dosomthing
};
const person_descriptor_t person_teach_desc =
{
"teacher",
//{0, 0},
sizeof(person_teach_t),
person_teach_ctor,
person_teach_dtor,
person_teach_modify,
person_teach_dosomthing
};
4. 虚函数/多态
多态,简单理解就是动态连接到通用函数
对于Person其中的Student和Teacher来说,操作接口已经封装好了,现在要做的就是在Student的时候,动态连接到Student的构造/析构/虚函数上去
下面是一个完整的创建Student学生信息的流程
module_person.c:
对外提供的API
int module_person_new(char *person_type, void *data)
{
person_class_t *pthis = NULL;
pthis = (person_class_t *)(PERSON_OBJECT.factory_ops->fac_new)(person_type, data);
printf("\nname:%s,age:%d\n", pthis->name, pthis->age);
return OK;
}
Person.c:
C++中每个声明了虚函数的对象都带有vptr(virtual table pointers),它是一个看不见的数据成员,指向对应类的virtual table,这个看不见的数据成员也称为vptr,被编译器加在对象里,位置只有才编译器知道
这里用的技巧是创建一个指针数组,讲子类描述符集合在一起,根据person_type来判断动态连接到哪个子类(相当于C++中的vptr,但是又和C++的RTTI机制类似)
_pthis->size这个size大小,根据动态得到的子类结构不同而大小不同
const person_descriptor_t* person_desc_types[] =
{
&person_stu_desc,
&person_teach_desc,
NULL
};
void *person_new(char *person_type, void *data)
{
person_descriptor_t *_pthis = NULL;
int type_idx = 0;
while (NULL != person_desc_types[type_idx])
{
if (!strcmp(person_type, person_desc_types[type_idx]->person_type))
{
_pthis = person_desc_types[type_idx];
break;
}
type_idx++;
}
(void *)pthis = (void *)malloc(_pthis->size);
* (const person_descriptor_t **)pthis = _pthis;
if (_pthis->desc_ctor)
{
pthis = _pthis->desc_ctor(pthis, data);
}
return pthis;
}
Person_student.c:
person_class_ctor用来创建子类间的公用信息,子类独有的信息,自己构造函数创建
void *person_stu_ctor(void *self, cfg_person_stu_t *data)
{
if ((NULL == self)||(NULL == data))
return NULL;
/* construct class data */
self = person_class_ctor(self, data);
if (NULL == self)
return NULL;
person_stu_t *pthis = (person_stu_t *)self;
pthis->score = data->score;
pthis->student_id = data->student_id;
strcpy(((person_class_t *)pthis)->person_type, "student");
return self;
}
Person_class.c:
Student和Teacher的公共数据信息,class就相当于C++中的虚基类,可以用下面模块来实现
list_add_tail(&(self->person_list), &(PERSON_OBJECT.person_list_head));是将类(这里可以理解为子类)的N多对象加到person的链表中方便管理
void *person_class_ctor(person_class_t *self, cfg_person_t *data)
{
if ((NULL == self)|(NULL == data))
return NULL;
strcpy(self->name, data->name);
self->age = data->age;
list_add_tail(&(self->person_list), &(PERSON_OBJECT.person_list_head));
return self;
}
上面是一个创建的多态基本流程,析构的过程刚好相反,虚函数同样机制
5. 小结
采用C面向对象来编码,模块清晰明了,同事与同事之间的工作独立性强,代码维护起来更容易,person.c/person_student.c/person_teacher.c/person_class.c的函数都非常好的隐藏起来,只有fac_new,fac_delete,fac_print暴露给上层API来操作
不同Module也可以采用类似来管理,这样就是网管NMS的顶层API---->多态Module---->factory_ops---->多态子类(基类Class)---->底层API
这样的效果就是不管底层API,或者上层API修改了,控制平面代码改动量也非常的小
上面采用C实现了基类,子类,继承,虚基类,构造函数,析构函数,虚函数,多态,Factory Method工厂方法,但是对比C++,比如friend,多重继承,等特性,C语言实现稍显麻烦,但是在嵌入式领域中这些C++的高级特性,至少在我参与的通信设备中,几乎用不到