## 面向什么,可能就会失去未面向的那些
在上文中模拟车链的程序中,我一开始是用面向对象的方式来写的,所以我造出了 5 个结构体,分别描述了二维点、矩形、圆形、链节形状以及链节等对象,结果却出现了一大堆繁琐的代码。虽然面向对象编程,在思维上是非常简单的,那就是现实中有什么,我们就模拟什么。但是你认真思考一下,现实中其实很多东西都有共性,如果你傻乎乎的去逐个模拟,而忽略它们的共性,那么你的代码绝对会非常臃肿。
当然,面向对象编程也提倡从所模拟的事物中提取共性,然后借助继承的方式来简化代码。但是一旦信仰了类与继承,你能做的最好的抽象就是对某一类事物进行抽象,比如你能够对『车』类的事物进行抽象,但是你却无法将对『飞机』和『车』这两类中的事物进行抽象。显然,飞机与车是有共性的,例如它们都能载客,都有仪表盘,都有窗户,都有座位,都有服务员……
当我发现基于面向对象创造的那些结构体存在着一个共性——它们都包含着两个成员,很自然的就会想到我应该制造一个包含着两个任意类型的结构体 `pair`,然后用 `pair` 来容纳我需要的数据。当面向对象编程范式在你的思想中根深蒂固,这种简单的现象往往会被忽略的,特别是你已经满足于你写的程序已经能够成功的运行之时。
接下来,当我试图用 `pair` 结构体取代二维点、矩形、圆形、链节形状等结构体的时候,我就开始走上了『泛型』的道路。C 语言里没有 C++ 模板这种工具可以用,所以我只能依赖 `void *`,而且为了简化 `double` 类型的数据向 `void *` 的转化,所以定义了:
~~~
double *
malloc_double(double x)
{
double *ret = malloc(sizeof(double));
*ret = x;
return ret;
}
struct pair *
pair_for_double_type(double x, double y)
{
struct pair *ret = malloc(sizeof(struct pair));
ret->first = malloc_double(x);
ret->second = malloc_double(y);
return ret;
}
~~~
如果你对 C++ 的泛型编程有所了解,一定会觉得 `pair_for_double_type` 函数其实就是对 `pair` 进行特化。因为本来我是希望 `pair` 能存储任意类型的数据的,但是现在我需要频繁的用它来存储一对 `double` 类型的数据,那么我就应该去制造一个专用的 `pair` 结构。
当我发现我需要频繁的产生 `pair` 实例,并向它的 `first` 与 `second` 指针中存储某些类型的数据存储空间的基地址,所以我就将这种共性抽象为:
~~~
struct pair *
pair(void *x, void *y)
{
struct pair *ret = malloc(sizeof(struct pair));
ret->first = x;
ret->second = y;
return ret;
}
~~~
最终使得 `create_chain_node` 函数的定义即简洁又清晰:
~~~
struct chain_node *
create_chain_node(void)
{
struct pair *left_hole = pair(pair_for_double_type(1.0, 1.0), malloc_double(0.5));
struct pair *right_hole = pair(pair_for_double_type(9.0, 1.0), malloc_double(0.5));
struct pair *holes = pair(left_hole, right_hole);
struct pair *body = pair_for_double_type(10.0, 1.0);
struct pair *shape = pair(body, holes);
struct chain_node *ret = malloc(sizeof(struct chain_node));
ret->prev = NULL;
ret->next = NULL;
ret->shape = shape;
return ret;
}
~~~
原来我用面向对象编程范式所写的代码是 104 行,换成泛型编程范式所写的代码是 75 行。那么我可以断定,是泛型编程拯救了面向对象吗?当然不能!因为我们的程序还没有写完,我们还需要面向对象。