可以将C头文件视为接口吗?

时间:2018-08-29 08:34:42

标签: c++ c architecture software-design clean-architecture

我正在从罗伯特·C·马丁(Robert C. Martin)的书清洁建筑了解建筑。本书强调的主要规则之一是DIP规则,该规则规定源代码依赖项必须仅向内指向高级策略。尝试在嵌入式域中进行翻译时,假设有两个组件schedulertimer。调度程序是高级策略,它依赖于低级计时器驱动程序,需要调用API get_current_time()set_timeout(),只需将模块拆分成一个实现文件timer.c和一个标头(接口?)timer.hscheduler.c可以简单地包含timer.h来使用这些API。阅读本书将先前的情况描绘为违反依赖关系规则,并暗示应在这两个组件之间实现接口以打破依赖关系。

例如,在c语言中,timer_abstract可以包含具有指向函数指针的泛型结构 struct timer_drv { uint32 (*get_current_time)(void); void (*set_timeout)(uint32 t); }

在我看来,这似乎是过度设计。一个简单的头文件还不够吗?可以将C头文件视为接口吗?

2 个答案:

答案 0 :(得分:1)

我认为您想要一个计时器接口的原因确实是打破了依赖关系。由于调度程序使用计时器,因此Scheduler.o的每个位置都已链接到该位置,因此,如果您使用依赖于计时器符号的调度程序符号,那么Timer.o也必须链接到该位置。

如果要为Timer使用接口,则不需要从Scheduler.o链接到Timer.o(或如果需要,也可以从Scheduler.so链接到Timer.so)。您将在运行时创建一个Timer实例,可能将其传递给Scheduler的构造函数,Timer.o将链接到其他地方。

现在为什么有用?单元测试就是一个例子:您可以将Timer Stub类传递给Scheduler的ctor并链接到TimerTestStub.o等。您可以看到这种工作方式确实破坏了依赖关系。 Scheduler.o确实需要一个Timer,但是在scheduler.so的构建时间级别上不是必需的,而是更高版本。您将Timer实例作为Scheduler ctor的参数传递。

这对于减少使用库时的构建时依赖性的数量也非常有用。真正的麻烦始于创建依赖关系链。调度程序需要计时器,计时器需要X类,X需要Y类,Y需要Z类... 这对您来说似乎还是可以的,但是您知道每个类都可以在另一个库中。 然后,您想使用Scheduler,但被迫拖动大量的includepath设置,并可能进行大量的链接。 您可以通过仅在调度程序界面中公开真正需要的Scheduler功能来打破依赖关系,当然您可以使用多个接口。

您应该进行自己的演示,编写10个类,将它们放入10个共享库中,确保每个类都需要10个共享库中的另外3个类。现在在main.cpp中包括这些类头中的1个,然后看一下需要做以使其正确构建。 现在您需要考虑打破那些依赖关系。

答案 1 :(得分:0)

在计算中,“接口”是两个或多个组件或子系统交换信息的公共边界。

C或C ++中的头文件是一个文本文件,其中包含一组声明和(可能是)宏,可以将其插入到编译单元(源代码的独立单元,例如源文件)中,并允许该编译单元使用这些声明和宏。换句话说,在后续编译之前,源文件中的#include "headerfile"被C或C ++预处理程序替换为headerfile的内容。

基于这些定义,我不会将头文件描述为接口。

头文件可以定义数据类型,声明变量和声明函数。多个源文件可能包含该标头,并且每个源文件都将能够使用在该标头中声明的数据类型,变量和函数。一个编译单元可以包含该标头,然后定义在标头中声明的某些(或全部)函数。

但是,类型,变量和函数不必放在头文件中。有足够的决心的程序员可以手动将声明和宏复制到使用它们的每个源文件中,而从不使用头文件。 C或C ++编译器无法分辨出差异-因为预处理器所做的只是文本替换。

声明和宏的逻辑分组实际上是表示接口的内容,而不是将有关接口的信息提供给编译单元的方式。头文件只是一个(可选)方式,通过它可以使编译单元可以使用一组声明和宏。

当然,通常使用头文件来避免在使用一组声明和宏时出现错误-因此可以帮助简化由这些声明和宏表示的接口的管理。 #include头文件的每个编译单元都接收相同的内容(除非受到其他预处理器宏的影响)。与程序员将声明手动复制到需要它们的每个源文件中相比,这种方法更容易出错。维护也更容易-编辑头文件意味着可以重建所有编译单元并可以看到更改。而手动更新每个源文件中的声明和宏可能会引入错误,因为程序员容易出错(例如,通过在源文件之间不一致地编辑声明)。