一、前言
在软件设计中,耦合性是用来衡量程序中各个模块之间相互关联程度的指标。这种关联度的高低,取决于模块间接口的复杂性、相互调用的方式,以及通过接口传递的信息种类。
编写高质量代码有两个核心追求:一是隔离变化,二是降低复杂度。而解耦,正是实现这两个目标的关键技术手段之一。它能让你的代码在面对需求变更时更加从容,结构也更清晰。
二、使用回调函数降低耦合
我们来通过一个具体的例子,看看回调函数是如何发挥解耦作用的。
问题描述:
需要实现一个 come_home 函数。它的功能是:传入一个出发时间,然后打印出回家的方式以及最终到家的时间。我们假设有两种回家方式:开车和走路。其中,开车回家需要1小时,走路回家则需要3小时。
功能分析:
由于回家方式不同,路上花费的时间也不同,从而导致到家的时间也不一样。我们可以把 come_home 函数内部的逻辑分为两部分:变化的部分和不变的部分。我们的目标是把变化的部分(即不同回家方式的具体逻辑)提取出来,封装成回调函数,从而达到隔离变化的目的。
- 变化部分: 输出具体的回家方式(如“开车”或“走路”),并根据出发时间,计算出对应的到家时间。
- 不变部分: 输出计算得到的到家时间。
这个设计思路的核心,在于利用C语言的函数指针,将可能变化的算法或行为“注入”到主流程中。想深入理解函数指针在C语言中的应用,可以到云栈社区的C/C++板块交流学习。
1、定义回调函数指针
首先,我们需要定义一个函数指针类型,来统一回调函数的“样子”。
typedef int (*on_arrival_t)(void* ctx, int departure_time);
这里,on_arrival_t 就是我们定义的回调函数指针类型。它指向一个函数,这个函数接收一个泛型上下文 ctx 和出发时间 departure_time 作为参数,并返回一个整型的到家时间。
2、编写come_home函数
接下来,编写主函数 come_home。它接受出发时间、一个回调函数以及一个可选的上下文参数。
void come_home(int departure_time, on_arrival_t on_arrival, void* ctx)
{
int arrival_time = on_arrival(ctx, departure_time); /* 变化部分:调用回调函数计算到家时间 */
printf(“arrival_time: %d\n”, arrival_time); /* 不变部分:打印到家时间 */
}
可以看到,come_home 函数本身并不关心具体是怎么回家的。它只是“约定”了一个计算到家时间的接口(即回调函数),然后把具体的计算工作交给了传入的 on_arrival 函数去完成。这就是依赖倒置的一种体现,主流程依赖于一个抽象的接口,而非具体的实现。
3、实现具体的回调函数
现在,我们来提供两种具体的回家方式实现。
/* 开车回家 */
int on_drive(void* ctx, int departure_time){
int arrival_time = departure_time + 1; /* 计算开车回家时间 */
printf(“drive\n”); /* 打印回家方式 */
return arrival_time;
}
/* 走路回家 */
int on_walk(void* ctx, int departure_time){
int arrival_time = departure_time + 3; /* 计算走路回家时间 */
printf(“walk\n”); /* 打印回家方式 */
return arrival_time;
}
这两个函数都严格符合 on_arrival_t 类型的定义。它们封装了各自独特的业务逻辑:计算耗时和输出方式。
4、main函数
最后,在 main 函数中,我们可以灵活地使用不同的回调函数来调用 come_home。
int main(int argc, char* argv[])
{
come_home(17, on_drive, NULL); /* 17点开车回家 */
come_home(17, on_walk, NULL); /* 17点走路回家 */
return 0;
}
5、输出
运行上述程序,你将会看到如下输出。这清晰地展示了同一主流程,通过注入不同的回调逻辑,产生了不同的行为结果。

通过这个简单的例子,我们演示了如何使用回调函数来解耦程序中的变化点。这种模式在事件驱动、异步编程、算法策略切换等场景中应用极为广泛,是理解和设计良好程序结构的重要基础。