山重水复疑无路
最后更新于:2022-04-01 06:25:44
## 山重水复疑无路
经过再次重构后的 `create_chain_node` 看上去要好了一些,但是依然有两段代码存在高度重复:
~~~
struct pair *left_center = pair_for_double_type(1.0, 1.0);
double *left_radius = malloc(sizeof(double));
*left_radius = 0.5;
struct pair *left_hole = malloc(sizeof(struct pair));
left_hole->first = left_center;
left_hole->second = left_radius;
struct pair *right_center = pair_for_double_type(9.0, 1.0);
double *right_radius = malloc(sizeof(double));
*right_radius = 0.5;
struct pair *right_hole = malloc(sizeof(struct pair));
right_hole->first = right_center;
right_hole->second = right_radius;
~~~
但是仅从 `pair` 结果体层面已经无法对这两段代码进行简化了,而且我又非常不想写一个像下面这样的辅助函数:
~~~
struct pair *
create_hole(struct pair *center, double radius)
{
struct pair *ret = malloc(sizeof(struct pair));
double *r = malloc(sizeof(double));
*r = radius;
ret->first = center;
ret->second = r;
return ret;
}
~~~
虽然 `create_hole` 能够将上述两段重复的代码简化为:
~~~
struct pair *left_center = pair_for_double_type(1.0, 1.0);
struct pair *left_hole = create_hole(left_center, 0.5);
struct pair *right_center = pair_for_double_type(9.0, 1.0);
struct pair *right_hole = create_hole(right_center, 0.5);
~~~
但是与 `pair_for_double_type` 函数相比,`create_hole` 这个函数的应用范围非常狭小。由于 `pair_for_double_type` 函数可以将两个 `double` 类型的数据存储到 `pair` 结构体中,在我们的例子中创建二维点与矩形可以用到它,在科学计算中创建极坐标、复数以及所有的二次曲线方程式也都都能用到它,但是 `create_hole` 却只能在创建车链这件事上有点用处。也就是说,正是因为 `pair_for_double_type` 函数所取得的成功,导致我们认为 `create_hole` 的品味太低。我们应该想一想还有没有其他途径可以消除上述代码的重复。
仔细分析 `left_hole` 与 `right_hole` 的构造过程,不难发现 `hole` 的 `center` 与 `radius` 这两种数据的类型不一致是造成我们难以对上述重复的代码进行有效简化的主要原因,`create_hole` 之所以能够对上述重复的代码进行大幅简化,是因为它根据我们的问题构造了一个特殊的 `pair` 结构体——姑且称之为 X。X 结构体的特殊指出在于其 `first` 指针存储的是一个面向 `double *` 的同构类型的 `pair` 结构体,其 `second` 指针则存储了一个 `double` 类型数据的基地址。正是因为 X 的结构太特殊了,所以导致 `create_hole` 这种抽象的应用范围过于狭隘,以至于现实中只有圆形比较符合这种结构体。
既然是异构的 `pair`,而我们已经实现了一个可以创建存储 `double` 类型数据的 `pair` 的函数 `pair_for_double_type`,这个函数的结果是可以直接存入异构 `pair` 中的。现在我们缺少只是一个可以将 `double` 值转化为可直接存入异构 `pair` 的函数,即:
~~~
double *
malloc_double(double x)
{
double *ret = malloc(sizeof(double));
*ret = x;
return ret;
}
~~~
有了这个函数,就可以对 `create_chain_node` 继续进行简化了:
~~~
struct chain_node *
create_chain_node(void)
{
struct pair *left_hole = malloc(sizeof(struct pair));
left_hole->first = pair_for_double_type(1.0, 1.0);;
left_hole->second = malloc_double(0.5);
struct pair *right_hole = malloc(sizeof(struct pair));
right_hole->first = pair_for_double_type(9.0, 1.0);;
right_hole->second = malloc_double(0.5);
struct pair *holes = malloc(sizeof(struct pair));
holes->first = left_hole;
holes->second = right_hole;
struct pair *body = pair_for_double_type(10.0, 1.0);
struct pair *shape = malloc(sizeof(struct pair));
shape->first = body;
shape->second = holes;
struct chain_node *ret = malloc(sizeof(struct chain_node));
ret->prev = NULL;
ret->next = NULL;
ret->shape = shape;
return ret;
}
~~~
而且,基于 `malloc_double` 函数,还能对 `pair_for_double_type` 函数进行简化:
~~~
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;
}
~~~
事实上,如果我们再有一个这样的函数:
~~~
struct pair *
pair(void *x, void *y)
{
struct pair *ret = malloc(sizeof(struct pair));
ret->first = x;
ret->second = y;
return ret;
}
~~~
还能对 `reate_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;
}
~~~
看到了吧,只要略微换个角度,很多看似难以简化的代码都能得以简化。这个简化的过程一直是在指针的帮助下进行的,但事实上,当你的注意力一直集中在怎么对代码进行简化时,指针的使用简直就是本能一样的存在,以至于你觉得你并没有借助指针的任何力量,完全是你自己的逻辑在指导着你的行为。在这个过程中,无论是面向对象还是面向模板,都很难将你从冗长的代码中拯救出来……