C 语言编程框架 LW_OOPC 介绍(三)
方案的可扩展性如何?
假设我们希望添加一种Ternary_node类型来表示三元操作符,如?:(也就是if-then-else操作符),看看,难度有多大?
事实上,正是因为前面的设计是面向对象的,要增加一种节点类型易如反掌:
// 三元表达式节点
CLASS(Ternary_node)
{
EXTENDS(Expr_node); // 继承Expr_node
char op[3]; // 假设操作符最长不超过2个字符
Expr* left;
Expr* middle;
Expr* right;
// 初始化三元表达式节点(传入一个操作符和三个子表达式)
void (*init)(Ternary_node* t, const char* op, Expr* left, Expr* middle, Expr* right);
};
在Expr中添加创建三元表达式的方法:
// 表达式(子树的概念),其中,init*方法族提供了构建子树的高层API,方便用户使用
CLASS(Expr)
{
int use; // 引用计数
Expr_node* p; // 子树的根节点
//…… // 既有实现
// 构建三元表达式(包含一个操作符,三个子表达式)
void (*initTernary)(Expr* t, const char*, Expr*, Expr*, Expr*);
// 构建三元表达式的重载形式(通过传入一个整型值参数,构造三个子表达式均为整数表达式的三元表达式)
void (*initTernaryX)(Expr* t, const char*, int, int, int);
//…… // 既有实现
};
请读者参照Binary_node的现有实现,实现出Ternary_node,这里不再赘述。一旦实现出Ternary_node,我们就可以这样创建表达式树并打印:
// …… // 创建expr1、expr2、expr3、expr对象(指针)
expr1->initUnaryX(expr1, "-", 0);
expr2->initUnaryX(expr2, "-", 5);
expr3->initBinaryX(expr3, "+", 3, 4);
expr->initTernary(expr, "?:", expr1, expr2, expr3);
expr->print(expr);
printf("\n");
为了支持新的节点类型,对原有代码的更动很少(仅对Expr类有增加方法),而且只有新增操作(新增类,新增方法),但没有修改操作(指修改原有方法),面向对象的设计赋予了系统极大的弹性,让程序在应对变化时,更加从容。在这个例子中,LW_OOPC帮助我们在C语言的世界里营造出OO的天地,带领我们再一次领略了面向对象的风采。
LW_OOPC最佳实践
说得简单一点,要想使用好LW_OOPC这套宏,还得首先懂面向对象,要遵循面向对象设计的那些大原则,比如开闭原则等。在C语言中使用面向对象,根据实际使用的情况,给出如下建议:
1) 继承层次不宜过深,建议最多三层(接口、抽象类、具体类,参见图 1和图 2)
继承层次过深,在Java/C#/C++中均不推崇,在C语言中实践面向对象的时候,尤其要遵循这一点,只有这样,代码才能简单清爽。
2) 尽量避免多重继承
尽可能使用单线继承,但可实现多个接口(与Java中的单根继承类似)。
3) 尽量避免具体类继承具体类
具体类继承具体类,不符合抽象的原则,要尽量避免。
4) 各继承层次分别维护好自己的数据
子类尽量不要直接访问祖先类的数据,如果确实需要访问,应当通过祖先类提供的函数,以函数调用的方式间接访问。
图 1
图 2
LW_OOPC的优点:
1) 轻量级
2) 广泛的适应性,能够适应各种平台,各种编译器(能支持C的地方,基本上都能支持)
3) 帮助懂OO的Java/C++程序员写出面向对象的C程序。
4) 使用C,也能引入OO的设计思想和方法,在团队的C/C++分歧严重时可能非常有用。
LW_OOPC的缺点:
1) 无法支持重载(C语言不支持所致)
2) 不完全的封装(无法区分私有、保护和公有)
LW_OOPC的INTERFACE/ABS_CLASS/CLASS三个宏展开后都是C语言的struct,其成员全是公有的,宏本身并无能力提供良好地封装层次的支持,所以,只能从编程规范和编程风格上进行引导。
3) 不支持RTTI
既然不支持RTTI,那么显然也无法支持安全的向下转型(C++中的dynamic_cast的转型功能)
4) 不支持拷贝构造以及赋值语义
5) 转换成接口的表述有点麻烦,表达形式相比C++要啰嗦很多。
6) 有学习成本,需要用户学习并习惯这套宏
前四条缺点,实质上并非是LW_OOPC的缺点,而是C相对C++而言的缺点,在这里,之所以也一并列上,是希望用户不要对LW_OOPC抱太高的期望,毕竟它也只是一套C语言的宏而已,C语言有的缺点,LW_OOPC并不能够解决。
总结:
尽管如此,在使用C语言编程的时候,在某些情形下,你可能想要通过面向对象来更好的组织代码。偶尔,你也想要用用某个设计模式,此时,这套宏能够帮得上忙,使用它,有助于写出相对易于理解和维护的面向对象的代码。因篇幅所限,本文中没有介绍LW_OOPC的高级特性,譬如对内存泄漏检测的支持。示例的完整代码、LW_OOPC的最新版本以及关于这套宏的更加详细的使用指南,请读者访问http://lwoopc.sourceforge.net获取。最后,期望有兴趣的读者,发挥聪明才智,提出改进建议,让LW_OOPC变得越来越好!
幕后花絮:
在完善LW_OOPC宏的过程中,我也认真研究了参考资料中列出的材料。最初V1.0版本有将近25个宏,后来,收到一些同事的反馈,认为少量宏晦涩难记,而且不易理解,经过认真考虑,删除掉5个宏,形成现在的20个宏。相比其他用C实现面向对象的方案,LW_OOPC简洁优雅,每个宏的命名都经过仔细地推敲,尽可能做到简单易懂,便于记忆。
但愿LW_OOPC真的能够帮助到奋斗在一线的C程序员们。
参考资料:
[1] 高焕堂。UML+OOPC嵌入式C语言开发精讲,电子工业出版社,2008 年9月。
[2] Object-oriented Programming with ANSI-C,下载地址:http://www.planetpdf.com/codecuts/pdfs/ooc.pdf。
[3] C实现面向对象的方法,http://www.eventhelix.com/RealtimeMantra/basics/object_oriented_programming_in_c.htm