用 C 语言实现 interface
本文是 C 语言封装设计的第四篇文章,前三篇请见《C 语言面向对象的封装方式》、《 C语言面向对象的封装方式(示例)》和《C语言数据结构的封装方法》。
本文介绍 C 语言中如何实现接口(interface),应用场景为:多个提供者提供的服务接口函数完全相同,但实现不同,因而在性能、可靠性等方面有所差异。比如,两个 HashMap 的实现,一个性能非常快,但内存占用大;另一个内存占用非常少,但性能一般。但二者提供的服务接口函数都是一样的,如 get、put 等。
通过 interface,我们可以把服务提供者的服务界面抽象成一致的函数群,调用者只需要对接口进行编程即可,无需关心接口下面的具体实现。这是对函数群的封装。
接口(interface)技术,是非常强大的技术。通过接口,调用者可以在对服务提供者完全无感知,甚至调用者的代码写完之后,还可以继续增加新的服务提供者,无需调用者的代码做任何更改。
比如,一个操作系统需要管理各种文件系统,但文件系统的种类是非常多的,无法把每一种文件系统都写入操作系统的内核中,因此内核可以对文件系统进行接口抽象,只要满足了这个接口要求,后续新的文件系统都可以注册进内核,无需对内核的代码进行任何修改。
与上面类似的业务场景,非常多,比如 I/O 的多种调度策略、复杂服务程序支持的各种插件接口、GUI 界面对 Window、Widget 的接口抽象等等。
我们用文件系统的场景,来展示 C 语言中如何设计和实现接口。
操作系统内核是调用者,各种文件系统是服务实现者,这些文件系统都实现了如下接口:
C 语言中,没有 interface 这个语法,因此上面的代码在 C 语言中,需要用 struct 来实现。具体包括:
每个接口函数,需要声明一个单独的函数指针类型;
整个 interface 的方法集,用一个 struct 来表示,struct 的成员为各个函数指针
每个文件系统的实现者,各自需要一个 struct 来表示,这个 struct 的类型对调用者不可见。各个文件系统有自己的 struct 结构,彼此互不相同,也互不可见。
接口的实现,包括两部分:1)接口函数的实现;2)文件系统的 struct 实例。这两部分放在一起,构成了接口的实现。我们用一个 struct 来把这两部分组合在一起。
由于各个文件系统的 struct 结构,对调用者不可见,因此文件系统用 void*把自己的 struct 指针传递给调用者。
具体的代码实现,如下:(为了保持篇幅简洁,只列了 open_file 和 read_file 两个方法)
上面的代码中,有几个地方需要注意:
1)每个接口函数类型声明中,都比 interface 中的函数多了一个参数:void* pfs, 这个参数指向具体的文件系统的 struct。
这样,内核才能真正对这个 struct 对象发起调用。
2)file_system_interface 是 interface 的具体实现体,里面包含 2 个指针:一个是指向文件系统实现体 struct 的指针 pfs, 另一个指针指向文件系统实现的接口函数的集合。
这样,interface 就是一个简单的 struct,可以像简单变量一样声明、赋值和参数传递,其按值拷贝传递即可,无需传指针引用。
这样,通过 file_system_interface,内核就可以对文件系统发起调用。例如,调用 ext2 文件系统的 open_file:
再看看 ext2 文件系统,如何实现这个接口:
ext2_fs.h 的内容如下:
ext2_fs.c 的实现代码:
其它文件系统,如 xfs,ext4 等,实现的代码与上面的 ext2 类似,这样就都可以注册进内核中。通过接口,内核可以对这些实现无感知。
接口,是非常强大的封装手段,在业务场景中经常遇到,希望通过本文的代码展示,让大家都学会这种技术。
我的微信号 "实力程序员",欢迎大家关注我。
版权声明: 本文为 InfoQ 作者【实力程序员】的原创文章。
原文链接:【http://xie.infoq.cn/article/2fdf2c32a8866f81978e18f18】。文章转载请联系作者。
评论