内容参考于《抽象接口技术和组件开发规范及其思想》
五.抽象接口与依赖反转
基于多态可以实现“与硬件无关”的应用程序。 在 C 编程中,多态的核心解决方法是充分利用“函数指针”,抽象接口就是只包含函数指针的类, 它们非常抽象,不包含任何具体的实现,仅定义了函数的调用规则。应用不在依赖具体的实现,根据接口去编写应用。实际上之前的综合示例最后一个示例已经完全体现。
1. 示例:
时间示例
接口与应用程序(与底层无关)
itime.h
struct tm
{
int tm_sec; // 秒, 0 ~59
int tm_min; // 分, 0 ~ 59
int tm_hour; // 小时, 0 ~ 23
int tm_mday; // 日期, 1 ~ 31
int tm_mon; // 月份, 0 ~ 11
int tm_year; // 年
int tm_wday; // 星期
int tm_yday; // 天数
int tm_isdst; // 夏令时(一般不使用,值为 0 或-1)
};
struct itime
{
int (*pfn_time_get)(struct itime *p_this, struct tm *p_tm);
int (*pfn_time_set)(struct itime *p_this, struct tm *p_tm);
};
static inline int itime_time_get(struct itime *p_this, struct tm *p_tm)
{
return p_this->pfn_time_get(p_this, p_tm);
}
static inline int itime_time_set(struct itime *p_this, struct tm *p_tm)
{
return p_this->pfn_time_set(p_this, p_tm);
}
app.c
void app_elec_watch(struct itime *p_time)
{
struct tm now_tm;
if (用户修改了当前时间)
{
struct tm set_tm = ... // 用户设置的时间
itim_time_set(p_time, &set_tm); // 设置时间
}
itime_time_get(p_watch->p_rtc, &now_tm); // 获取当前时间
printf("Now time is : %04d-%02d-%02d %02d:%02d:%02d \r\n",
now_tm.tm_year + 1900, now_tm.tm_mon + 1, now_tm.tm_mday,
now_tm.tm_hour, now_tm.tm_min, now_tm.tm_sec);
// ...其它处理, 如在 LCD 上显示时间等
}
某一型号为 PCF85063 的 RTC 芯片
底层实现
pcf85063.h
#include "itime.h"
struct pcf85063
{
struct itime itime;
};
#endif /* __PCF85063_H */
pcf85063.c
#include "pcf85063.h"
static int __pcf85063_time_get(struct itime *p_this, struct tm *p_tm)
{
// 首先,从 PCF85063 中获取出年、月、日、时、分、秒等信息
if (获取成功)
{
p_tm->tm_year = // 从 PCF85063 中获取出的 年 信息
p_tm->tm_mon = // 从 PCF85063 中获取出的 月 信息
p_tm->tm_mday = // 从 PCF85063 中获取出的 日 信息
p_tm->tm_hour = // 从 PCF85063 中获取出的 时 信息
p_tm->tm_min = // 从 PCF85063 中获取出的 分 信息
p_tm->tm_sec = // 从 PCF85063 中获取出的 秒 信息
return 0; // 获取时间成功
}
return -1; // 获取时间失败
}
static int __pcf85063_time_set(struct itime *p_this, struct tm *p_tm)
{
// 首先,将 p_tm 中的年、月、日、时、分、秒等信息设置到 PCF85063 中
if (设置成功)
{
return 0;
}
return -1;
}
struct itime *pcf85063_init(struct pcf85063 *p_dev);
{
//... PCF85063 硬件初始化
p_dev->itime.pfn_time_set = __pcf85063_time_set;
p_dev->itime.pfn_time_get = __pcf85063_time_get;
return &p_dev->itime;
}
main.c
#include "pcf85063.h"
static struct pcf85063 pcf85063_dev;
int main()
{
struct itime *p_itime = pcf85063_init(&pcf85063_dev);
app_elec_watch(p_itime); // 启动“电子表”应用程序
while (1);
}
2. 分析
示例中,应用程序、 接口与实现类之间的关系
传统设计应用程序和实现的关系
在面向过程的实现中,main->app->pcf85063。如果pcf85063有变化和改动的话,将会影响到依赖他的上层。而这里,app依赖的是接口,只要接口不变,app是完全不会被底层影响到的。main提供什么给app,app就使用什么。如果有更多的底层,那么关系如下
所有具体的实现类都要基于接口类中定义的抽象方法来实现(功能、 函数原型等由抽象方法规定),因此,通常情况下,接口类会被很多具体的实现类所依赖,若修改了接口类,则改动会影响所有的实现类,影响范围很大。基于此,接口类应该是非常稳定的, 不应该轻易变化。 实际上, 这对接口类的定义提出了很高的要求, 接口类不能随便定义,应考虑到可能的变化,合理、正确的定义各个抽象方法。
这里APP不再依赖底层,高层模块不再依赖于低层模块,而是APP依赖的是上层main传入的参数。依赖关系颠倒了。这就是依赖反转。