创建:2009年12月26日
关于MVC中C的讨论
一,关于C有三种实现,比较它们的不同。
关于C有三种实现:MFC中的View或Dialog;iPhone中的Controller;Java的Struct的Action;
*MFC中的View实现的最简陋了。它既包含C的成分,又包含了V的成分(例如,保持View在最前端等等)。
对于程序后添加的按钮等控件,则保存在了RC文件中,但是RC文件的改变是需要重新编译的。
*iPhone中的Controller前进了一步,它把Controller的部分完全独立了出来。它与MFC的主要区别是:MFC使用类的继承,在View的子类中添加了Controller的逻辑。而iPhone则使用接口和组合将C的逻辑独立出来。
并且,对于程序后添加的按钮等控件,则保存在了NIB文件中。对NIB的改变不需要重新编译。
--缺点是:IPhone的Controller还是捎带着牵连进来了一部分View的逻辑。例如需要在响应函数-(IBAction)xxxx:(id)sender中操作sender,而sender就是GUI因素,需要通过它来得到空间的内容。而且还需要利用IBOutlet来改变界面,而IBOutlet也是GUI界面。而且在View中还需要如UIAlertView来提示客户。这些缺点导致对于Controller还是无法进行测试的。
*Java的Struct则更前进了一步。Struct中C的逻辑是完全放到Action中的,而且Action中一点View的逻辑都没有。它可以独立测试。
它是怎样实现的呢?过程如下:1,客户端点击某个表单的按钮--->2,IE将其发送到WebServer--->3,WebServer调用Struct--->4,Struct根据HTTP协议解析表单,并将其内容保存到全局区间--->5,Struct根据请求的Action的名字读取配置文件得到响应的Action,并调用--->6,Action向全局区域获取想要的内容--->7,Action操作之后,将结果添加到全局区域,并返回相应View的名字--->8,Struct得到View的名字,然后根据配置文件调用相应的View--->9,View也向全局空间得到自己需要的内容,并显示--->10,Struct将View发送给客户端。
二,讨论我们是否能在MFC和iPhone中模仿Struct。
答案是不能。直接原因有4个,根本原因有4个。
直接原因是:1,需要编写大量代码,降低开发时间;2,不容易维护和调试;3,代码逻辑混乱;4,增加运行开销;
根本原因是:1,界面逻辑不同;2,协议标准不同;3,语言特性不支持;4;不必要。
*首先将根本原因1-界面逻辑不同。例如在单机GUI中时常需要弹出对话框,但是对于Struct面对的Web环境,则不需要弹出对话框,或者说所谓的对话框也是通过View来实现的。它不能通过一个关闭按钮回到原来的状态中,只能点击IE上的后退按钮;同时,对话框有那种弹出的效果,而Web页面则是并列的关系,虽然在MFC中也能这样做,但是这会让用户十分不习惯,同时也要多写很多代码。
这就导致,如果需要提示对话框,Action则直接返回一个View的名字,而MFC则不能这么做,即使能这样做,也将非常麻烦。
*其次讲根本原因2-协议标准不同。Struct使用HTTP协议返回客户表单,因此Struct能够自动化的处理表单,并将对应的变量保存到全局区域。而MFC则没有标准,只是一个按钮触发一个操作,要获取其他控件的内容(也就是表单内容),则需要自己手动的去获得。因此,我们要想将这些内容保存到全局区域,则需要手动操作,这将导致编写很多代码。但是这一原因其实并不能算是不能模仿Struct的很根本原因,为什么呢?因为即使不模仿Struct,MFC也还是要执行这个操作的,因此无论是否模仿,这个操作都避免不了,因此说它不是影响模仿Struct的根本原因。
*其次讲根本原因3-语言特性不支持。MFC不支持反射,和通过名字创建对象。这将导致后面的直接原因。
*其次我们将讲全部的4个直接原因。让我们先假设模仿Struct,然后说说哪些影响了开发效率。
要模仿Struct,即将所有的逻辑都放入Action中。这有三种实现方法:
a,Action子类的成员函数中保存了所有它需要的所有内容,然后由Controller在调用的时候负责赋值,内容来自两方面,一是当前控件内容的;另外一方面是全局空间原有的内容的。
然后,Controller还要负责把Action执行的结果读出来写入控件,并把需要保存到全局空间的内容写入到全局空间。
--缺点是:1)全局空间的内容本来是各个Action通用的,而这种方法导致Action中额外多几个成员变量保存全局内容。2)对于全局空间的赋值也是额外的操作,包括向Action赋值,以及读取Action的值写入全局空间。--这两点的工作量,以及空间和运行效率都降低了,并且也要增加大量的工作量。
b,针对a的改进是,将全局的内容统一保存到一个公共的全局类里面,每个Action都有它的一个引用。
--缺点是:1)由于全局类包含了所有公共内容,所以当任何一个Action的内容改变,需要添加或减少一个公共变量时,都将引起全部的Action的重新编译。这明显的违反了“向稳定的方向依赖”这一原则。这一方法肯定是错误。
c,针对b的改进是,仍然使用全局类,但是类的成员变量使用一个Map,是公共成员名到它的内容的映射。这样,添加新的公共变量时,不需要改变全局类,所以全部的Action也不用重新编译了。
--缺点:1)导致所有的公共变量失去了类型标记。每次赋值和取值都要各进行一次类型转换。这与原来的MFC程序相比,不但增加大量工作量,而且容易出错。(但是Struct却也是忍受了这种工作量和这种可能出错的危险。)2)也不便于调试。比如,在VC6.0中我们查看Map的内容十分费劲;同时,由于公共变量是动态加入的,因此设置内存断点也比较麻烦。
--但是,Struct其实就是这么实现的。只不过编辑器和调试器给了更多的帮助。比如Struct利用HTTP协议将自动化装在全局变量的过程;调试器也可以查看全局的区域的内容;但是,它仍然要付出“不能检测类型”的风险。
*但是,我们已经和真正的Struct很接近了,因为Struct也是忍受了这种工作量和这种可能出错的危险。只要我们不介意调试器带来的不便,我们也可以这样实现我们C++版本,或者Objective-C版本的Struct。
真正的问题是“根本原因4不必要”。我们比较一下:
Struct的好处是:1,可以单独调试Action;2,Controller可以和所有Action子类解耦;3,可以使用拦截器;4,Action和View可以单独设计;
MFC模仿Struct的好处是:1,可以单独调试Action;2,Controller可以和所有Action子类解耦;
MFC模仿Struct的缺点是:1,需要编写大量代码,降低开发时间;2,不容易维护和调试;3,代码逻辑混乱;4,增加运行开销;
不必要的原因是,对于MFC和Objective-C的程序而言,程序规模小,我们对于“1,可以单独调试Action;2,Controller可以和所有Action子类解耦;”是否真的那么需要,以至于可以忍受它所带来的缺点。对于“可以单独调试Action”,由于Action的数量少,逻辑简单,我们即使在出现问题后在调试也很快就能定位,而且,必要的时候,对于一些复杂的逻辑逻辑,我们也可以再抽取出Action来进行测试,而不必所有的逻辑都用Action来实现,因此,第一个好处就不必要了。而对于“Controller可以和所有Action子类解耦”,同样,MFC很少的逻辑,即使所有逻辑都写在View中,也不要紧,所有第二个好处也没有用了。--总之,MFC模仿Struct是没有必要的。如果加入Struct只改动很少的内容,则还可以考虑一下,如果代价那么昂贵,我们就没有必要模仿它。
那么为什么对于MFC程序不必要的东西,丢与Struct程序就必要呢?因为:1)Struct是服务器程序,逻辑庞大,程序也庞大,一点点的牺牲,可能大幅度提高程序的开发效率。2)客户端发送HTTP到服务器端,无论如何服务器端也是要解析HTTP的,并保存响应的变量,而且这个过程可以自动化。而对于MFC,我们则需要手动的将控件的值写入全局变量,然后再由Action读出来,然后Action在将结果写进去,Controller在读出来。这与原来的“Controller读出控件数据,然后运算结果,然后写到控件中”这一过程相比,麻烦了不少。也就是说Struct是必要的过程,但是在MFC中却一点也不必要。
三,讨论我们可以借鉴些什么。
如果需要单独测试某段逻辑的功能。则可以使用方法a来建立一个Action,把Controller中逻辑提取出来。然后单独测试Action。
四,总结
通篇,我们最终论述了MFC和iPhone是不能模仿Struct的。附带的,我们有时可以借鉴一点,以达到测试的目的。
+++++