Contents
完善MVC的DEMO,闲话MAKEFILE(中)
我们回到MVC模块的讨论。现在C基本搞定,M和V还没有确认。假设我们有这样的设计任务。
mode = 0 表示在屏幕上打印一个直角三角型
mode = 1 表示在屏幕上打印一个倒直角三角型
mode = 2 表示在屏幕上打印一个等腰三角型。
height 表示高度
width 表示长度
那么我们需要把代码做一定的调整。这里我们就看一下模块化编程的优势。 首先,我们得找到control模块关于config文件读取的数据位置。 其次,数据获取后,我们需要将数据传递给model,model进行计算加工。 最后,model计算完毕后,需要给予view。view需要进行显示。
鬼话:模块化编程,关注两个。数据流向,数据组成结构(非数据结构)。通常是先列出数据流向(注意不是程序流程图,完全两回事),再分别确认后者。 因此,上面是数据流向。下面我确认数据组成结构。
鬼话:数据组成结构,和画画一样,由粗到细。不求精,但求清。清楚的说明这个数据组织的结构存在的价值,作用,和整体生命周期和资源占用情况。
通常我们从view开始,因为这样比较方便验证嘛。view的原理也简单点。我们假设存在一个存储区域,这个区域和屏幕大体查不多。然后逐个显示嘛。
我们需要修改value和view。记得存储空间资源,我们统一在value里进行申请和使用。这里给出设计书写步骤。
- 我们先在view里,修改write_param_to_file,替换为output_data,反正局部函数,无所谓要view标记一下,control.c里也可以有个静态的output_data函数嘛。
- 去除char v_param[1024];
- 引入define_attr.h,将printf替换为__PRINT_FUNC();
- 修改view,毕竟这个函数作为主入口,为了后续增加内部模块的功能,我们不能 swtich 和一堆堆的if进行比较。模块化编程,讲究架构确定,动态改变的修改尽可能的少。
- 增加一个view_reflash的函数。这个是为了将外部存储空间的地址引入当前文件的局部存储指针。 但暂不考虑测试,因此注释起来。
- 先写些数据存储空间和对应结构。
代码如下:
#include <stdio.h>
#include "define_attr.h"
static P_VIEW_OUTPUT_S s_poutput;
static int output_data(void *pdata){
int i;
FILE *f = (FILE *)pdata;
__PRINT_FUNC();
for (i = 0 ; i < s_output->lines ; i++){
fprintf(f,"%s\n",s_poutput->buf[i]);
}
return 0;
}
#if 0
static int view_reflash(void *pdata){
s_poutput = (P_VIEW_OUTPUT_S)pdata;
return 0;
}
#endif
void view(int flag,void *pparam){
if (flag >= MAX_VIEW_TYPE_SIZE){
printf("error flag!\n");return;
}
view_done[flag](pparam);
return;
}
显然这个不能被编译,链接,甚至执行。因此我们得加快调整一些代码。我们需要增加以下内容。
- P_VIEW_OUPUT_S对应的结构体的声明,我们至少设计了lines,表示行数,buf表示字符串存储空间两个内容。
- MAX_VIEW_TYPE_SIZE和实际的flag以及对应的view_done,函数指针数组需要迅速定义,声明出来。
- 实际的VIEW_OUTPUT_S需要在value.c里尽快处理。
继续之前,我们讨论下,当一个新模块,有大量代码需要增加时,如何递进的开发。基本要保持以下几个要求。
- 逐步的代码增加。
- 每步都可以测试。
- 虽然逐步,但尽可能的让每步的代码,可以更好的为后续代码开发使用。
特别是第3点,估计新手有点晕了。我们举个例子。公司一堆事情要开展工作。怎么办?肯定能先做什么做什么,当然每做一件,尽可能的规范,类似的事情的后续开展,有个流程可依,有个规范可鉴,由此,同类的事情,对于后续你的工作无非两个。 一个督导别人按照原有方式开展。 一个参与或旁观同类事件对流程规范的影响,以做调整。 如同我们替换掉了write_param_to_file这个函数。并非说,前面的规范或以做的流程是一成不变的。根据实际情况需要调整,但调整局部而非整体。
你也可以想想,你在建个桥,先用辅助设备,例如脚手架或其他支撑件把整体架构支撑起来。等真的桥墩好了,那么桥墩作为稳定的模块,来支撑后续整体结构,你原先的架子,该撤就撤。 而新手容易犯个毛病。让他盖楼,喜欢把一楼都装修好了,再考虑二楼水电。最终无非两个结果,现场混乱,错误百出。
回来说一下为什么上面这个阶段至少写了这么多。
首先,我们需要有测试,尽可能的另测试部分接近实际模块的某个功能点。 因此output_data是需要写的。当然我们用了最简单的逻辑来实现。就是printf一堆字符串,至于以后是能用2D或3D甚至网络方式实现,那是后话。
for (i = 0 ; i < s_output->lines ; i++){
fprintf(f,"%s\n",s_poutput->buf[i]);
}
现在保证了,这个函数的逻辑特性。 其次,为了保证这个函数的逻辑特性的描述,数据区域,和行数需要出现了。由此,引发VIEW_OUTPUT_S这个结构体的呈现。
随后,一个客观现实,output_data的函数,算是view这个模块接口的下属功能操作,因此,为了view的后续功能的有效增加,我们需要规范view接口内部的调用方式。由此view做了如下调整 view_doneflag; 这是为了我上面说的,第3点,逐步开展,但尽可能的让每步的代码可以更好的位后续代码开发使用。对应也不得不增加了flag的判断。
鬼话:函数指针数组的使用,会在模块化编程中经常出现,如果你是一个比较专业的模块化设计的人员。但指针数组,即涉及数组这个有限存储空间,也涉及函数地址跳转,两个非常容易导致跑飞的事故发生。因此,看见函数指针数组,要如同看见美女一样,随时保持兴奋状态,仔细打量,数组下标的范围检测是否存在。
鬼话2:函数指针数组,通常使用在模块与模块的匹配互联上。你甚至可以想想这是一个地址译码器,或者片选逻辑,原则上,对于模块的局部函数,你可以不用检测(我们说了。局部函数的逻辑和数据来源都可以通过外部接口处的逻辑进行检验以保证逻辑正确性),但对于接口部分,例如view这个对外的函数,一定要做好逻辑检测。
由此,我们需要额外进行代码设计。以保证上面新增代码的有效性。我说了数据的空间申请,都需要在value.c里统一处理。或许你现在就迫不及待的开始在value.c文件里修改。若你看我做了什么。我写下下面的代码,view.c的清单如下
#include <stdio.h>
#include "define_attr.h"
enum {
_VIEW_OUTPUT,
_VIEW_REFLASH,
_VIEW_MAX_TYPE_SIZE
};
typedef struct{
int lines;
char buf[10][100];
}VIEW_OUTPUT_S ,*P_VIEW_OUTPUT_S;
typedef int (*_VIEW_FUNC)(void *);
static VIEW_OUTPUT_S output;
static P_VIEW_OUTPUT_S s_poutput = &output;
static int view_output(void *pdata);
static int view_reflash(void *pdata);
static _VIEW_FUNC view_done[_VIEW_MAX_TYPE_SIZE] = {view_output,view_reflash};
static int view_output(void *pdata){
int i;
FILE *f = (FILE *)pdata;
__PRINT_FUNC();
for (i = 0 ; i < s_poutput->lines ; i++){
fprintf(f,"%s\n",s_poutput->buf[i]);
}
return 0;
}
static int view_reflash(void *pdata){
#if 0
s_poutput = (P_VIEW_OUTPUT_S)pdata;
#endif
int i,j;
__PRINT_FUNC();
s_poutput->lines = 2;
for (i = 0; i < 2 ; i++){
for (j = 0 ; j < (i+1)*2 ; j++){
s_poutput->buf[i][j] = '*';
}
s_poutput->buf[i][j] = 0;
}
return 0;
}
void view(int flag,void *pparam){
#if 0
if (flag >= _VIEW_MAX_TYPE_SIZE){
printf("error flag!\n");return;
}
view_done[flag](pparam);
#endif
view_done[_VIEW_REFLASH](0);
view_done[_VIEW_OUTPUT](stdout);
return;
}
对应,我们将view.h修改为如下:
#ifndef _VIEW_H_
#define _VIEW_H_
void view(int,void *);
extern char v_param[];
#endif
同时,对attr.c的main函数修改如下:
int main(int argc ,char *argv[]){
#if 0
FILE *f = 0;
int view_mode = 0;
int control_mode = 0;
#endif
view(0,(void *)0);
return 0;
....
为什么这么做?其实就一个目的,快速测试。测试哪些东西?为什么优先测试这些东西?
我们测试了view的调用,view_done的调用,view_output的调用。注意这里并没有测试view_reflash的调用。只是借助它给予测试数据生成使用。
鬼话:数据流向,是我们优先确定的。上述的工作就是先把数据流向的通道初步构件起来。引水先挖沟嘛。
需要注意,对于这种临时代码用于做局部测试的,我们没有必要规规矩矩。所以#if 0屏蔽掉正常的和历史遗留尚未处理的代码。现在编译运行,看屏幕上是否有
**
****
的输出。
同时,没错,我们也没有在view_reflash中,严格的用宏的方式处理相关数据。这也是因为,此是临时测试文件使用。讲究快,有效,清晰,在短时间内,能将该测试的处理掉,测试什么?数据流向,稳定后是函数于函数的调用,因此这些辅助的测试内容没有必要太较真,毕竟后续的规范测试,会替代这些临时内容。
你也可以看到,typedef struct {} VIEW_OUTPUT_S;enum等等,我也一股脑的写在了这个C文件里。按理说,应放在h文件里,以方便value.c的使用。
尽可能的在两个测试环节中,对改动的代码范围和内容缩小,这将有利于新测试时,发现问题的解决。如果存在另一个BUG,当你把上述typedef 或enum放入view.h时。那么你的新测试就会出现寻找验证两类问题的工作。
鬼话: 1+1=2,这个没错,但如果两个1同时存在,组合出来的复杂度一定比依次出现的复杂度要高很多。我们尽可能的把简单的事情搞复杂,就似乎希望令复杂的每个小模块足够小到非常容易解决,分而治之的将一个“简单”问题处理掉。专家都这么干,你不是专家无非你尚不具备搞复杂的能力。
我们验证成功,下面的工作就是着重对数据存储空间的处理。以下先给出view.h的内容
#ifndef _VIEW_H_
#define _VIEW_H_
enum {
_VIEW_OUTPUT,
_VIEW_REFLASH,
_VIEW_MAX_TYPE_SIZE
};
#define dVIEW_HEIGHT 10
#define dVIEW_WIDTH 200
typedef struct{
int lines;
char **buf;
}VIEW_OUTPUT_S ,*P_VIEW_OUTPUT_S;
void view(int,void *);
extern char v_param[];
#endif
这里我们将buf定义为二级指针类型。并存在 dVIEW_HEIGHT ,dVIEW_WIDTH的空间定义。为什么用二级指针类型?因为我们可以在value统一处理申请和释放。value.c的修改后的清单如下:
#include "value.h"
#include <stdlib.h>
#include "control.h"
#include "view.h"
#define FREE_S_POINT(s) do {if (s) {free(s); s = 0 ; } }while (0)
#define FREE_S_G_POINT(s,g) do {if (s) {free(s); s = 0; g = 0;} }while (0)
static int init_flag;
static char *s_pmodel = 0;
static char *s_pcontrol_input = 0;
static char **s_pview_output;
static int init_control(void){
if (s_pcontrol_input == 0){
s_pcontrol_input = g_pcontrol_input = (char *)malloc(sizeof(char)*CONTROL_INPUT_SIZE);
return 1;
}
return 0;
}
static void free_control(void){
FREE_S_G_POINT(s_pcontrol_input,g_pcontrol_input);
}
static int init_model(void){
if (s_pmodel == 0){
s_pmodel = g_pmodel = (char *)malloc(sizeof(char)*100);
return 1;
}
return 0;
}
static void free_model(void){
FREE_S_G_POINT(s_pmodel,g_pmodel);
}
static int init_view(void){
if (s_pview_output == 0){
int h;
char *p;
s_pview_output = (char **)malloc(sizeof(char)*dVIEW_HEIGHT*dVIEW_WIDTH + sizeof(char *)*dVIEW_HEIGHT);
p = (char *)&(s_pview_output[dVIEW_HEIGHT]);
for (h = 0; h < dVIEW_HEIGHT;h++ ,p+= dVIEW_WIDTH){
s_pview_output[h] = p;
}
}
view(_VIEW_REFLASH,s_pview_output);
return 1;
}
static void free_view(void){
FREE_S_POINT(s_pview_output);
}
static int init_status(void){
g_status = 1;
return 1;
}
static void free_status(void){
return;
}
int init_all(void){
if (init_flag == 1) {return init_flag;}
init_flag = 1;
init_flag = init_flag & init_status();
init_flag = init_flag & init_model();
init_flag = init_flag & init_control();
init_flag = init_flag & init_view();
return init_flag;
}
int get_init_status(void){
return init_flag;
}
void free_all(void){
if (init_flag == 0) {return;}
init_flag = 0;
free_view();
free_control();
free_model();
free_status();
return;
}
int g_status;
char *g_pmodel ;
char *g_pcontrol_input;
这里有几个地方要注意。对于init_view的函数,free_view的函数,由于前面的代码已经约束了类似的函数的实现内容和调用方式,因此你可以很容易集中关注新增内容的设计,而降低了,当前新工作和value.c或value这个模块的关联。这是模块化编程的一个实践思想。
对于s_pview_output我们并没有对应的全局指针类型存储空间。而是使用了view(_VIEW_REFLASH,s_pview_output);将新生成的空间传导到view模块,这是好事情。谁说一定要用全局存储空间来存放指针的?模块要安全嘛。但一定要注意,此时等同于value.c模块和view.c模块产生了耦合关联,后面会讲到这样做带来的麻烦事。不过我们可以通过模块的一些解耦合操作来处理掉。
二级指针类型申请二维空间,我们始终将所有需要申请的空间连在一起。这样对于free_view的实现会非常方便。而且实现方式近似其他已经写过的方式。
对应的view.c修改如下:
#include <stdio.h>
#include "define_attr.h"
#include "view.h"
typedef int (*_VIEW_FUNC)(void *);
static VIEW_OUTPUT_S output;
static P_VIEW_OUTPUT_S s_poutput = &output;
static int view_output(void *pdata);
static int view_reflash(void *pdata);
static _VIEW_FUNC view_done[_VIEW_MAX_TYPE_SIZE] = {view_output,view_reflash};
static int view_output(void *pdata){
int i;
FILE *f = (FILE *)pdata;
__PRINT_FUNC();
for (i = 0 ; i < s_poutput->lines ; i++){
fprintf(f,"%s\n",s_poutput->buf[i]);
}
return 0;
}
static int view_reflash(void *pdata){
int i,j;
__PRINT_FUNC();
s_poutput->buf = (char **)pdata;
#if 1
s_poutput->lines = 2;
for (i = 0; i < 2 ; i++){
for (j = 0 ; j < (i+1)*2 ; j++){
s_poutput->buf[i][j] = '*';
}
s_poutput->buf[i][j] = 0;
}
#endif
return 0;
}
void view(int flag,void *pparam){
if (flag >= _VIEW_MAX_TYPE_SIZE){
printf("error flag!\n");return;
}
view_done[flag](pparam);
return;
}
注意这里,view函数我们已经开始规范化设计。而同时,在value.c里,也存在通过flag = _VIEW_REFLASH,由view_reflash进行了对s_poutput->buf的刷新。同时,view_reflash中有#if 1。暂时表示这里的代码,也是临时测试辅助代码。
我们在看下attr.c的变动。此时可不能简单的调用view。摘抄attr.c的main函数部分如下
int main(int argc ,char *argv[]){
FILE *f = 0;
int view_mode = 0;
int control_mode = 0;
if (init_all() == 0){
{
printf("init not finish! system return ...\n");
return 1;
}
}else{
atexit(free_all);
}
view(_VIEW_OUTPUT,stdout);
return 0;
...
view(_VIEW_OUTPUT,stdout);
调用可不能在init_all前调用。当然你要注意attr.c需要#include "view.h"
鬼话:可能对于新手,到目前还是没有适应,一个模块对应的存储空间被value统一处理,而且在主函数中看不到显示调用,这种设计方式。没关系。当你能对模块化编程方式方法熟练时,这些的抽象理解都不是问题。同时我上述的所有做法,也仅是模块化编程方法的一种。我觉得挺有效,好不好自己判断。
现在我们需要对view的显示数据进行剥离。我们尝试将 view_reflash的临时代码放到外面配置。哪?显然是model嘛。但model需要配置两类数据。一类是实际显示的内容,一类是存储空间区域的信息。那么同样的道理,我们先仅在view内完成这样的工作。再尝试将实际调用转移到model去
#include <stdio.h>
#include "define_attr.h"
#include "view.h"
typedef int (*_VIEW_FUNC)(void *);
static VIEW_OUTPUT_S output;
static P_VIEW_OUTPUT_S s_poutput = &output;
static int view_output(void *pdata);
static int view_reflash(void *pdata);
static int view_set_output_info(void *pdata);
static _VIEW_FUNC view_done[_VIEW_MAX_TYPE_SIZE] = {view_output,view_reflash,view_set_output_info};
static int view_output(void *pdata){
int i;
FILE *f = (FILE *)pdata;
__PRINT_FUNC();
for (i = 0 ; i < s_poutput->lines ; i++){
fprintf(f,"%s\n",s_poutput->buf[i]);
}
return 0;
}
static int view_reflash(void *pdata){
int i,j;
s_poutput->buf = (char **)pdata;
__PRINT_FUNC();
#if 1
view(_VIEW_SET_OUTPUT_INFO,(void *)2);
#else
s_poutput->lines = 2;
#endif
for (i = 0; i < 2 ; i++){
for (j = 0 ; j < (i+1)*2 ; j++){
s_poutput->buf[i][j] = '*';
}
s_poutput->buf[i][j] = 0;
}
return 0;
}
static int view_set_output_info(void *pdata){
int lines;
__PRINT_FUNC();
lines = (int)(long long int)pdata;
s_poutput->lines = lines;
return 0;
}
void view(int flag,void *pparam){
if (flag >= _VIEW_MAX_TYPE_SIZE){
printf("error flag!\n");return;
}
view_done[flag](pparam);
return;
}
对应view.h的enum我们修改为
enum {
_VIEW_OUTPUT,
_VIEW_REFLASH,
_VIEW_SET_OUTPUT_INFO,
_VIEW_MAX_TYPE_SIZE
};
其余均不改变。编译链接执行。哇塞,估计有人要说了,不带这样占篇幅的。就把一行语句替换了,多增加了一个函数,也值得算个步骤?
从代码设计量来说,真不算什么。但对理解模块化编程来说,确实值得阶段化说明。你可以看到上述代码设计有以下几个优势
- 新增模块,只需要增加对应内容,而无需修改view函数的内部结构。
鬼话:面向对象流行了很久。一个经常被炫耀的地方就是继承,数据,实现函数的继承。那么面向模块的设计,只能低调的介绍,我们针对系统框架和数据流向模式的继承。面向模块,面向过程的语言,例如C。确实有所不足,但由于C语言的灵活强大,这些不足,总能通过各种设计方法进行弥补。这里只是我野鬼的一种设计方法,如果你多看看各种高质量的C语言代码,你会发现,C代码是一种艺术,而C++是一种学术。
- 配置参数和实际数据产生了剥离。这对模块化编程非常有效。经常出现一种设计任务,数据始终是异步流动的,但控制参数是同步等待确认的,将实际数据和配置参数的剥离,对代码逻辑符合现实情况有帮助。但面向对象设计这类问题,有拖家带口的感觉。
下一步,我们开始处理model。和view一样。先给出早期的代码清单
#include <stdio.h>
#include <setjmp.h>
#include "value.h"
#include "define_attr.h"
jmp_buf context_buf;
typedef void F_V_V(void);
static int test = 0;
static void model_init(void);
static void model_done(void);
static void model_exit(void);
#define MODEL_ENTRY_NUM 4
static F_V_V *model_entry[MODEL_ENTRY_NUM] = {model_init,model_done,model_exit,model_exit};
static void model_init(void){
int t;
__PRINT_FUNC();
if ( (t = setjmp(context_buf)) >= 10){
printf("my god ,i escape! %d\n",t);
model_exit();
}
return;
}
static void model_done(void){
int i;
__PRINT_FUNC();
for (i = 0 ; i < 100; i++){
printf("touch the dog! \n");
if (test >= 10){
printf("the officers come ! run away!\n");
longjmp(context_buf,test);
}
test += 1;
}
return;
}
static void model_exit(void){
__PRINT_FUNC();
return;
}
void model(int status){
status = (status >= MODEL_ENTRY_NUM ) ? MODEL_ENTRY_NUM-1: status;
model_entry[status]();
}
再给出调整后的代码清单,以做对比
#include <stdio.h>
#include <setjmp.h>
#include "value.h"
#include "define_attr.h"
#include "view.h"
jmp_buf context_buf;
typedef void F_V_V(void);
static int test = 0;
static void model_init(void);
static void model_done(void);
static void model_exit(void);
#define MODEL_ENTRY_NUM 4
static F_V_V *model_entry[MODEL_ENTRY_NUM] = {model_init,model_done,model_exit,model_exit};
static char s_model_buf[5][200];
static char *s_pmodel_buf[5] = {(char *)&(s_model_buf[0]),
(char *)&(s_model_buf[1]),
(char *)&(s_model_buf[2]),
(char *)&(s_model_buf[3]),
(char *)&(s_model_buf[4])};
static void model_init(void){
int t;
__PRINT_FUNC();
if ( (t = setjmp(context_buf)) >= 10){
printf("my god ,i escape! %d\n",t);
model_exit();
}
return;
}
static void model_set_mode0(int height ,int width){
int i,j;
for (i = 0; i < height ; i++){
for (j = 0 ; j < width * (i + 1) ; j++){
s_pmodel_buf[i][j] = '*';
}
s_pmodel_buf[i][j] = 0;
}
view(_VIEW_REFLASH,s_pmodel_buf);
view(_VIEW_SET_OUTPUT_INFO,(void*)(long long int)height);
view(_VIEW_OUTPUT,stdout);
}
static void model_done(void){
int i;
__PRINT_FUNC();
for (i = 0 ; i < 100; i++){
printf("touch the dog! \n");
if (test >= 10){
printf("the officers come ! run away!\n");
longjmp(context_buf,test);
}
test += 1;
}
return;
}
static void model_exit(void){
__PRINT_FUNC();
return;
}
void model(int status){
__PRINT_FUNC();
model_set_mode0(3,2);
#if 0
status = (status >= MODEL_ENTRY_NUM ) ? MODEL_ENTRY_NUM-1: status;
model_entry[status]();
#endif
}
对应attr.c的main 函数做如下改动
int main(int argc ,char *argv[]){
FILE *f = 0;
int view_mode = 0;
int control_mode = 0;
if (init_all() == 0){
{
printf("init not finish! system return ...\n");
return 1;
}
}else{
atexit(free_all);
}
model(0);
return 0;
...
main函数就不多说了。说说为什么我贴出model.c的前后对比代码清单。
- 我们的代码,虽然整体架构越来越固定,但实际代码可能经过一段时间的开发后,变的面目全非。但这需要逐步完成。推倒从来不是个好方式。
- 代码之所以可能在未来面目全非,是因为不同阶段时的代码作用不一样。模块化 编程,针对框架和模块结构,通常包含大量代码。而这些代码和实际的具体业务设计没有关系,但却可以支撑灵活的业务调整,以及保证整体代码的质量。包括稳定性,扩展性,易测试等。
另我们要拓展讨论一下model.c此阶段的设计。
你可以看出我们对model原有代码进行了屏蔽。甚至可能被屏蔽的代码会替换如view那样的方式。不过此时并不是来优化model函数的实现方式。而是尽快将model模块内修改数据的工作和view的模块进行联通。
view的接口决定了,给入的数据指向为二级指针类型,因此,我们手工创造了一个s_model_buf,和一个s_pmodel_buf,这是一个指向指针的数组类型。记得它是个数组,有5个存储空间,每个空间存放的值被看作一个char类型空间的地址。此处可不能写成指向数组的指针。因为这样,我们无法存在实际存储的指针的空间,view里的是二级指针。搞不懂?回头看我前面的内容。
鬼话:别以为我野鬼喜欢创造名词。二级指针,而不是如三维数组说二维指针,是为了我自己头脑不混乱,代码不出错。用不用随你。
view(_VIEW_REFLASH,s_pmodel_buf);
有没有搞错?竟然会抹掉value.c里对view的空间配置。估计有人要叫了。
多大事啊。首先,view的显示空间并不是归属view的。value.c才是拥有者。其次,只要空间有效,view并不会不工作。view和操作和数据空间剥离了。 鬼话:叫嚣面向对象优势的学院派,复读机的赞美,数据和操作的捆绑。在某些设计问题上,我会在游行现场卖着茶叶蛋的鼎力支持,顺带送瓶矿泉水的时候,也一样振臂奇喊,面向对象好,面向对象好,面向对象大家值得搞。但是在另一些设计问题上,将数据可操作进行捆绑就是一种灾难。而面向模块化设计的思想,就是剥离操作和数据的关联。
view(_VIEW_SET_OUTPUT_INFO,(void*)(long long int)height);
view(_VIEW_OUTPUT,stdout);
这跟的紧啊。为什么?因为测试要加快。此处只是临时测试代码。你完全可以在这两句前后加上#if 1 #endif
鬼话:实际写代码时,一定要有种大冷天,连喝10瓶可乐的感觉。啥?挡不住的感觉。看到厕所就冲刺。而对于你在敲击键盘时,你就要有种需要立刻测试的强烈欲望。相信我,如果你有10个步骤,一口气写完,一次性测试,如果时间小于或等于分10次写完,10次测试,那么绝大多数情况下,日后会有故事,通常故事的结局,需要你更长的时间来获取。
执行后,我们会发现有了如下打印
view_reflash func!
view_set_output_info func!
model func!
model_set_mode0 func!
view_reflash func!
view_set_output_info func!
view_set_output_info func!
view_output func!
view_reflash func! 出现两次,我们好理解。value模块在申请空间时,折腾了一次,model测试运行时,申请了一次。但为什么view_set_output_info func!出现了3次。
对了。view_reflash函数,还有临时测试版本呢。如果我们尝试如下设计代码
static void model_set_mode0(int height ,int width){
int i,j;
__PRINT_FUNC();
for (i = 0; i < height ; i++){
for (j = 0 ; j < width ; j++){
s_pmodel_buf[i][j] = '*';
}
s_pmodel_buf[i][j] = 0;
}
view(_VIEW_REFLASH,s_pmodel_buf);
view(_VIEW_SET_OUTPUT_INFO,(void*)(long long int)height);
view(_VIEW_OUTPUT,stdout);
}
这是打印个3*2的矩型。相信我,输出一定不是你想要的。这里给出一个大大的鬼话:
我的性子很急,现在仍然很急。但是写代码,我的性子越来越慢。不是心脏不好了。而是被摔怕了。只要快跑,就摔跤。曾经对自己的评价,没学会走就想跑?现在对自己的定型,我只适合走,但凡两条腿同时离地,一定摔跟头。不要觉得 __PRINT_FUNC();很无聊。也不要觉得屏幕打印一堆函数调用的前后顺序没有意义。这里不就是从函数输出中,判断出了一个BUG吗?而且恰好这个BUG你无法从输出结果中判断。
由此我们修改view.c的一个函数如下
static int view_reflash(void *pdata){
int i,j;
__PRINT_FUNC();
s_poutput->buf = (char **)pdata;
return 0;
}
这样再运行试试。看看是否能仅通过 model_set_mode0 的调整,随心所欲的打印出正方型,三角型了?