电子说
>>> 1.5.4 实现接口
为了描述事物的完整性和相对封闭性,“封装”就提上了日程,细节从此不需要再去关注。而封装的传统定义是数据隐藏,如果还是这样看待封装,则具有很大的局限性。应该将封装视为任何形式的隐藏,即发现变化将其封装。封装不仅可以隐藏数据,而且可以隐藏实现和隐藏设计等所有的细节。
如果以更宽泛的方式看待封装,其优点是能够带来一种更好的分解程序的方法,于是封装层自然而然地就成为了设计需要遵循的接口。封装不会妨碍人们认识程序内部具体是如何实现的,只是为了防止用户写出依赖内部实现的代码。进而强迫用户在调用程序时,仅仅依赖于接口而不是内部实现,使抽象的概念接口和实现分离,将大大降低软件维护成本。
C语言中的*.c文件就是接口功能的具体实现,即用户不可见的内部实现,简称实现。一个接口可以有多个实现,它在发布后还可以改变、升级,因为它的改变不会对调用程序产生影响。大多数时候,*.c和*.h是成对出现的,一般来说,将某个子模块的声明放在*.h文件中,而将具体的实现放在对应的*.c文件中。*.c文件可以通过引用一个或多个*.h文件,达到共用各种声明的目的,但是*.h文件不可以引用*.c文件。
其实软件包就是一个用来描述定义一个库的软件,其中*.h文件作为库的接口,而实现这个库可能有一个或多个*.c文件,每个*.c文件包含1个或多个函数定义,软件包就是由*.h文件和*.c文件所组成的。这是一种良好的风格,适用于任何大型程序和小型程序。
假设开发一个由多个文件组成的大型程序pgm,这样就需要在每个*.c文件的顶部都放上这样一行:
#include "pgm.h" // 用户自己编写的库文件
由此可见,通过共性分析使设计具有比较强的内聚,其价值就是实现紧凑的设计。从而使调用者无需关注实现的细节,实际上是函数的实现与使用它们的函数解耦了,swap()接口的实现程序清单 1.17。
程序清单 1.17 swap数据交换接口的实现(swap.c)
1 #include "swap.h"
2 void swap(int *p1, int *p2)
3 {
4 int temp;
5
6 temp = *p1; *p1 = *p2; *p2 = temp;
7 }
当p1和p2分别指向变量a和b时,则p1和p2存储的值就是&a和&b,即可用*p1和*p2表示a和b的值。如果写成以下这种形式:
temp = p1;
则交换的不是a的值,而是a的地址(p1的值就是a的地址)。而函数要交换的是a和b的值,不是它们的地址。因此需要使用*运算符和指针,该函数才能访问存储在这些位置的值并改变它们。即指针允许将局部变量的地址传给函数,然后在函数中修改局部变量。
由此可见,当将问题的“共性和可变性”分离开来,经过简化后发现,稳定不变的相同的处理部分(temp = *p1; *p1 = *p2; *p2 = temp;)都包含在抽象的模块中,可变性分析所发现的变化的变量a和b由外部传递进来的参数应对。从软件设计学角度来看,共性和可变性分析原理自然而然地成为了面向过程编程的理论基石。
注意,编写代码必须遵循结构化编程规则,即每个函数、函数中的每个代码块都应该只有一个入口、一个出口。实际上,只有在大函数中,这些规则才会有明显的好处。刚开始写代码时,都会冗长而复杂。有太多的缩进和嵌套循环,有过长的参数列表,甚至还会有重复的代码。需要不断打磨这些代码,分解函数、修改名称、消除重复,并保证测试通过。
有时我们并不关心指针所指向的变量的类型,此时可以使用并不指定具体数据类型的泛型指针void *。通常只允许相同类型的指针之间进行转换,但泛型指针能够转换为任何类型的指针,反之亦然。比如,C标准库中的memcpy()函数它将一段数据从内存中的一个地方复制到另一个地方。由于memcpy()可能用于复制任何类型的数据,因此将它的指针参数设定为void指针是非常合理的。比如,此前的swap()函数,可以将它的参数改为void指针,则swap()就变成了一个可以交换任何类型数据的通用交换函数,详见程序清单 1.18。
程序清单 1.18 swap()函数(void_data_swap.c)
1 #include
2 #include
3
4 int swap(void *x, void *y, int size)
5 {
6 void *temp;
7
8 if((temp = malloc(size)) == NULL)
9 return -1;
10 memcpy(temp, x, size); memcpy(x, y, size); memcpy(y, temp, size);
11 free(temp);
12 return 0;
13 }
>>> 1.5.5 使用接口
只要传入待交换的变量的地址,即可确定如何通过接口调用它们,详见程序清单 1.19。
程序清单 1.19 swap数据交换函数范例程序
1 #include
2 #include "swap.h"
3
4 int main(int argc, char *argv[])
5 {
6 int a = 1, b = 2;
7
8 printf("%d, %d\n", a, b);
9 swap(&a, &b);
10 printf("%d, %d\n", a, b);
11 return 0;
12 }
由此可见,抽象的接口隐藏了它的内部细节,用户不再依赖具体的实现代码,而是依赖于抽象接口。抽象的接口几乎没有细节,没有什么需要变化的,使抽象和细节彼此隔离,因此抽象的接口非常容易被重用,其深刻地揭示了抽象的生命力。
全部0条评论
快来发表一下你的评论吧 !