软件设计中各种接口随处可见,如函数接口、子系统或模块之间的接口、不同产品之间的接口等,而接口抽象历来是“仁者见仁,智者见智”。一般情况下,我们得到的只是一些接口抽象的基本原则、建议,而程序员在具体开发中则大有“天高任鸟飞”的趋势,不同程序员几乎总可以抽象得到不同的接口。
本文并不打算深入讨论接口抽象的原则或建议,仅针对子系统或模块之间的接口抽象,介绍两种典型的接口抽象结果,即函数接口与消息接口,至于如何选取便由程序员根据产品特色而决定了。
在介绍两种接口抽象结果之前,先大概介绍一种产品开发中最常见的树状软件系统架构,如下图所示。一般产品的软件系统由多个子系统组成,每个子系统又由若干模块组成,部分模块可能需要进一步细分为多个子模块,因此模块或子模块是软件系统的基础架构成分。当然,软件系统架构会因产品而异,下图仅是一种最常见的软件系统架构。本文论述中将模块作为最底层的开发单元,仅为阐述简洁。
以下分别讨论函数接口与消息接口。
1、 函数接口
所谓函数接口是指模块之间通过函数进行交互,以完成特定功能及信息传递。简而言之,函数接口本身便是一种模块之间的直接函数调用,例如模块A在某功能中直接调用模块B的相应函数,以实现相关功能。如下代码所示,TDM模块中函数TDM_SetStm1()需要调用模块BSP中的函数BSP_SetFpga()才可完成相关功能的设置,此时两个模块之间便是通过函数接口来交互的。
void TDM_SetStm1(…)
{
......
BSP_SetFpga(…);
......
}
正常情况下,程序员并不喜欢自己模块中的函数被其它模块直接调用,尽管这样更快捷点,但由于增加了耦合故而维护或优化起来极为痛苦。因此,一般情况下,模块对外提供统一接口,将相应内部函数进行封装,而不同模块交则通过统一函数接口进行交互。例如,如果模块A需要实现多个功能,而每个功能需要调用模块B不同的函数,此时模块B只需要将待调用的函数封装到一个对外函数接口中,模块A直接调用该接口,而不同功能则可通过参数进行区分。至于对外函数接口的抽象,并不是本文重点,但封装抽象后的函数接口内部如何根据不同功能进行驱动,则是后续表驱动文章中的内容了,不再赘述。
尽管函数接口的方式便于理解整个功能的调用链,是符合逻辑思维的抽象结果,而信息传递主要借助形参完成,但当模块之间的交互非常频繁是维护起来便是灾难。例如,不同模块之间信息交互最常见的方式,便是函数接口的形参中使用C指针以传递内存地址,但如果某指针既需要传入又需要传出的话,即仅变更其某些字段的取值,那对该指针的使用便要十分小心,以免变更了错误的字段。在我的产品经历中,便发生过类似的错误,即模块A调用模块B中的统一对外接口时传入了静态结构数组的指针,但模块B对此是一无所知的,导致结构体中的一个字段会累加赋值,从而引入故障。更可悲的是,该故障在产品大规模商用的近两年后才被偶然发现!
总而言之,函数接口最终呈现出来就是C/C++中最常见的函数调用,只不过调用者与被调用者分属不同的模块而已。
2、 消息接口
所谓消息接口是指子系统或模块之间通过消息进行交互,以完成特定功能及信息传递。消息接口不像函数接口那样是一种身体力行的方式,其更关注消息的交互流程。如下图所示为一种常见的模块间消息交互流程,模块A先发送Request消息至模块B,模块B收到该消息后进行相应处理,继而发送Respond消息给模块A,以反馈处理结果。
消息接口本质上是一种信号传递,而这种方式也是人类沟通的主要形式。例如,去麦当劳用餐时,顾客首先要告诉服务员所点食物,相当于顾客向麦当劳发出了Request消息;而服务员在下单后告知厨房准备食物,相当于麦当劳在处理Request请求;当食物准备完成后再由服务员递给故而,相当于麦当劳给顾客返回了Respond响应。很多时候,仅传递信号是不够的,还需要传递相关的信息,而消息接口中的信息传递一般通过内存段实现。例如,去麦当劳用餐时顾客不能仅告诉服务员“我要吃饭”,必须进一步告知其具体的食物名称。
消息分为同步消息与异步消息两种。同步消息是指发送方在消息发送至接收方后,必须等待对方的回应才可以处理后续事物,而在收到回应之前的等待时间内是不能处理其它事物的;异步消息是指发送方在消息发送至接收方后,无需等待对方回应可转而处理后续事物,而对方处理完消息后再给发送方回应即可。例如,顾客去麦当劳用餐,一般情况下点餐付款完成后,服务员在将所点食物递给顾客后,才可以招待下一位顾客,而中间是不会被其它顾客打断的,这便与同步消息的处理类似。又例如,去中餐馆吃饭时一般都是点餐后告知服务员,然后一直等待上菜,而服务员在完成菜单下单后便去响应其他顾客了,并不会在旁边一直等待菜品上桌,直到顾客用餐完后找服务员付款,这便与异步消息的处理类似。
严格地讲,函数接口的处理流程本身便是同步的,即处理过程中不可能给其它事物打断,甚至可将函数接口认为是同步消息接口的特例;而消息接口的处理流程则因消息种类而异,同步消息是不允许被其它事物打断的,但异步消息天生便会转而处理其它事物。一般软件设计开发中,广泛应用的是异步消息,但特殊情况下必须使用同步消息。
无论函数接口还是消息接口,均是软件设计开发中广泛使用的两种接口形式,至于孰优孰劣根本无从定论。软件设计需要考虑系统架构的层次关系、模块耦合、功能聚合等等,纯粹地从接口形式判断孰优孰劣是武断的,必须结合模块具体功能及上下文模块关系而定。例如,最顶层的UI或管理软件与下层模块交互时广泛使用消息接口,而同一子系统内部功能耦合严重的模块间可能更倾向于函数接口。至于选取何种接口,不是本文的论述重点,本文仅简介两种常见的抽象结果。
以上讨论了两种最常见的子系统或模块之间的接口抽象结果,仅为本人在产品开发中的一些经验,可能存在局限性,且谬误之处在所难免,仅为进一步讨论抛砖引玉。