MFC五大批判
算起来,我用Visual C++也有将近5年的历史了。在这期间,我也曾涉猎过Visual Basic和Delphi,但都是浅尝而止;Visual C++始终是我的主业。可是努力的成果如何呢?我用Delphi作出了十多个有规模的软件,用VB--虽然我用在VB上的时间只有短短的两三个月--也有两个像样的项目;然而,在我付出了最大热情和最多努力的Visual C++上面,却只作出了三个自己看得上眼的软件。
固然,在用Visual C++的时候,MFC帮了我不少的忙。但是,在写下这个题目之时,我就已经打定主意:在这篇文章中,只对MFC提出批评,不说MFC的好话。Visual C++的拥护者且慢发难,听我道出其中原因。我注意到,象候捷先生这样对MFC极其热爱的著者,在其大著《深入浅出MFC》中对MFC的评价也是尽量的做到客观和公允;而大师Charles Petzold和Jeff Prosise,在他们的作品中也只是给予MFC以谨慎的赞美。Charles Petzold还很客气的指出了MFC的局限。然而另外一些编程书籍的作者,特别是某些国内的作者,似乎毫不吝惜把最华丽的语言和最夸张的赞美赋予 MFC,从书架上任意翻开一本介绍Visual C++的书籍,看看它的前言和序章,往往充斥着让人目眩的溢美之辞。多少初学者被这些充满暗示和诱导的辞令吸引,以为MFC是完全可视化的,象VB一样容易掌握的东西,当他们深入以后,会不会有上当的感觉呢?我痛恨一切不负责任的夸大和炫耀,特别是只为了增加书籍销量而不惜昧着良心说话的作者,而我的感觉是现在这样的作者和书籍似乎已经泛滥了。本着矫枉必须过正的指导思想,我的目的很明确,就是要批评MFC。对Visual C++和MFC非常熟悉的读者,我无虑您对本文提出批评和指责,因为您对MFC已经有了自己的观点,不会为我所误导;对Visual C++的入门者,我希望您在听够了对Visual C++和MFC的赞美之后,来听听另一种声音,即使它并不完全正确(甚或是充满谬误),至少能让您带着自己的思想来看待您将要学习的东西。
对MFC的批判之一:不支持属性,MFC凭什么同其他语言抗衡?
窃以为在编程语言中引入“Property”的概念是在面向对象的编程思想后最为重大的革新之一。其实,目前市场上绝大部分编程语言,包括VB, Delphi,C++Builder和PowerBuilder等等都支持Property。程序员只要简单的修改对象的属性,就能够完成相当部分的工作,不仅是减少了无谓的劳动,更重要的是减少了出错的机会,并且使得生成更复杂的界面和完成更复杂的工作成为相对容易的工作。我想绝大部分人会同意,如果去掉了Property这个东西,那么象VB和Delphi这样好的RAD,包括Microsoft一直倡导的ActiveX,都会失去了绝大部分的魅力。这个道理,Microsoft应该是在推出Visual Basic 1.0的时候就认识到了。可是自从Visual C++诞生到现在,它似乎丝毫没有使用Property的意思,虽然Visual C++这个名字在很大程序上沾了它的长兄Visual Basic的光,不过它并没有从VB那里学到如何让编程更简单和更轻松的秘诀。
有人可能会说,data member of class不是property吗?不是的,如果你用过C++Builder的话你一定能明白这种分别。MFC从来就不支持Property,而且今后看来也不会了,这意味着用MFC,你还是得干苦力活。(ActiveX?不错,ActiveX支持Property,而且MFC支持ActiveX开发,不过这并不是三段论发挥作用的地方。)
对MFC的批判之二:单调的处理方式使本来应该简单的工作变得复杂
应该没有人反对这样的观点:用Visual C++开发界面,特别是不符合Microsoft所谓“标准”的程序,比VB,Delphi或是C++Builder都要慢得多。(附带说一句,我不知道 Microsoft制定Windows Logo标准,并且要求程序员遵守的依据是什么;我自己的程序99%以上都不符合这个标准。)可这是为什么呢?是C++语言在这方面的天生不足?肯定不是。
在我看来,罪魁祸首是MFC中的CDocTemplate,这个类规定了一种非常死板和机械的机制,即一个Document,一个View和一个FrameWnd绑定在一起。遗憾的是,实际情况往往复杂的多。对界面稍微稍微要求高一点的程序大多要求一个Document有多个View,甚至在某些程序中,希望在同一个View中显示多个Document的内容:比如,将两个公司的业绩放在一起作比较。对View和FrameWnd的关系也有类似的情况。然而,DocTemplate的机制使得这样并不高的要求变的相当困难。想实现你的要求吗?可以。你要添加新的View类。你要从默认的 IDR_MAINFRAME复制资源到新的类中。你要用AddDocTemplate添加自定义模板。你要用
GetFirstDocTemplatePosition和GetNextDocTemplate检索模板列表。你要用GetDocString察看每个模板是否符合你的要求。你要重载CFrameWnd::OnCreateClient以派生新的视图。你要用CView::SetDlgCtrlID和 CFrameWnd::SetActiveView以及CFrameWnd::RecalcLayout来在各个视图中切换。你要用未公开的 CDocManager管理文档模板。你要...还要吗?反正我是怕了。
对MFC的批判之三:固步自封,不思进取
MFC 可以作为固步自封的活教材。别忘了,MFC是和Borland OWL同一时代的产物(还有多少人记得OWL呢?)当然,这不是MFC的错误。不过,如果以个类库的体系自从2.0版本以来就没有丝毫改变,是不是意味着这个类库已经臻于完美了呢?不,即使Microsoft也不敢这么狂妄。但事实是,MFC的体系从MFC2.0以来就没有变动过。每个版本的更新,不过是增加了一些新类,某些类的接口稍作修改,仅此而已。不,不要把ATL作为MFC的改进;ATL从来就不依赖于MFC。
谁都知道在这几年中C++语言有了多么大的改进。包括RTTI,Dynamic Creation,Exception,Standard Template Library等等都成为新的C++标准的一部分。不过,Microsoft好像并不喜欢这些新东西,它的做法是另起炉灶;于是在同以套Visual C++中就出现了两套互不兼容的实现。平心而论,在新的C++标准出台前,Microsoft自己实现这些机制实在是一个相当了不起的创举;但是历史总是在发展的,MFC为什么不从善如流,尽量利用语言中已经实现的功能,而非要固执的用自己的一套老办法?事实上,MFC几乎没有用到新的C++方案中任何有益的元素--尽管这些方案已经对MFC库中很多问题提出了相当完美而且精练的解决方法。
对MFC的批判之四:天然的倾向性
不知道您对AppWizard生成的默认项目有什么感觉。反正我的感觉是:这种工程就是用来开发象Word,Excel这样的程序的。好像MFC天然的就倾向于这样一种程序。但事实上,这种程序少之又少。
在Microsoft 看来,好像每个程序都应该有一个File菜单,而且这个菜单下面一定应该有New,Open,Save,Exit这些选项。在我的实际经验来说,我只搞过一个程序符合这样的要求。有多少人真的要搞一个自己的字处理程序或是电子表格呢?对于很多常见的、基于数据库的程序,你需要New,Open和Save 吗?如果是基于网络的程序呢?特别是在多媒体程序和游戏程序中,MFC生成的框架与其说是帮助,不如说是累赘。
这倒是符合Microsoft的一贯风格:你要的只是特定的功能,它却一并塞给你一大串不相干的东西,并且在很多时候,这些不相干的东西反而成为麻烦制造者。于是,我不得不在新生成一个项目后,不辞劳苦的去掉AppWizard“慷慨”的赠送品,包括大量无用的菜单和工具条按钮,然后才能开始实际的工作。说实在的,AppWizard在为我减少工作量的同时,也增加了我不少劳动量。
MFC定义了一个Document-View框架,并且把Document定义的相当宽泛,几乎可以代表任何数据。但是在实现上,Document是相当狭窄的;比如Document定义的Serialize固定的与一个CArchive对象联系在一起,而CArchive又固定的与一个CFile联系在一起,这样实际上就限定Document处理的对象只能是磁盘文件。况且,在一个 Serialize中处理所有数据的序列化也是一种相当机械而死板的机制:它只能处理小量而且是一次处理完毕的数据,而实际上,程序往往要处理大块的数据,并且不可能一次完成。在这种情况下,CDocument的处理机制反而是个障碍。此外,在很多类型的程序中,CDocument扮演的是一个很尴尬的角色:比如在绝大部分数据库程序中,CDocument完全是个鸡肋,实际的数据处理只能靠CRecordset来完成;再比如说,在最典型的Doc- View程序--就是记事本程序--中,CDocument根本是个无能的东西,因为它存取数据反而要求助于CEditView:: SerializeRaw。
Doc-View框架有可能突破这些限制吗?完全可能。不过,你要有心理准备,如果你要这么作,你就必须求助于一大堆的未公开函数和类型,这些东西完全没有文档,依赖于你对MFC“底下的东西”究竟有多么熟悉,以及你是否愿意钻研MFC的源代码--在绝大多数情况下这不是一件愉快的工作。如果你使用这些未公开的东西出了问题,或者你不知道如何使用,对不起,Microsoft不会给你任何支持--谁教你不按照 Microsoft的逻辑工作来着!
对MFC的批判之五:什么是封装?
好像这不成为一个问题。但是对MFC而言,封装的定义是不成立的。这句话的意思是说,如果你不想生产玩具的话,如果你需要调试程序的话,如果你的代码需要有比 AppWizard更多的东西,那么,MFC对你来说就不是封装的。如果你的程序运行出了错,对不起,问题不在你的代码里面,而在MFC库的某个文件中, Visual C++会为你打开这个文件,至于“为什么会出错?”“这个函数究竟是用来干什么的?”MFC不会给你答案,你自求多福吧。如果你除了MSDN中的公开文档以外,对MFC的源代码从不关心,那么恭喜你,你是个快乐的程序员,但是你永远不能生产出真正有用的程序。
候捷先生在其著作《深入浅出MFC》中也曾提到:或许有人会产生疑问,追寻“黑盒子底下的东西”,岂不有违面向对象编程的初衷?但是没有办法,不去熟悉MFC的源代码,永远只能生产玩具。候先生说的很委婉,尽量不批评MFC。但是,我可以这样说:使用VB,Delphi,C++Builder,Java这些语言的程序员几乎从来不去关心类库的源代码,可是这些语言并不是用来生产玩具的!
固然,在用Visual C++的时候,MFC帮了我不少的忙。但是,在写下这个题目之时,我就已经打定主意:在这篇文章中,只对MFC提出批评,不说MFC的好话。Visual C++的拥护者且慢发难,听我道出其中原因。我注意到,象候捷先生这样对MFC极其热爱的著者,在其大著《深入浅出MFC》中对MFC的评价也是尽量的做到客观和公允;而大师Charles Petzold和Jeff Prosise,在他们的作品中也只是给予MFC以谨慎的赞美。Charles Petzold还很客气的指出了MFC的局限。然而另外一些编程书籍的作者,特别是某些国内的作者,似乎毫不吝惜把最华丽的语言和最夸张的赞美赋予 MFC,从书架上任意翻开一本介绍Visual C++的书籍,看看它的前言和序章,往往充斥着让人目眩的溢美之辞。多少初学者被这些充满暗示和诱导的辞令吸引,以为MFC是完全可视化的,象VB一样容易掌握的东西,当他们深入以后,会不会有上当的感觉呢?我痛恨一切不负责任的夸大和炫耀,特别是只为了增加书籍销量而不惜昧着良心说话的作者,而我的感觉是现在这样的作者和书籍似乎已经泛滥了。本着矫枉必须过正的指导思想,我的目的很明确,就是要批评MFC。对Visual C++和MFC非常熟悉的读者,我无虑您对本文提出批评和指责,因为您对MFC已经有了自己的观点,不会为我所误导;对Visual C++的入门者,我希望您在听够了对Visual C++和MFC的赞美之后,来听听另一种声音,即使它并不完全正确(甚或是充满谬误),至少能让您带着自己的思想来看待您将要学习的东西。
对MFC的批判之一:不支持属性,MFC凭什么同其他语言抗衡?
窃以为在编程语言中引入“Property”的概念是在面向对象的编程思想后最为重大的革新之一。其实,目前市场上绝大部分编程语言,包括VB, Delphi,C++Builder和PowerBuilder等等都支持Property。程序员只要简单的修改对象的属性,就能够完成相当部分的工作,不仅是减少了无谓的劳动,更重要的是减少了出错的机会,并且使得生成更复杂的界面和完成更复杂的工作成为相对容易的工作。我想绝大部分人会同意,如果去掉了Property这个东西,那么象VB和Delphi这样好的RAD,包括Microsoft一直倡导的ActiveX,都会失去了绝大部分的魅力。这个道理,Microsoft应该是在推出Visual Basic 1.0的时候就认识到了。可是自从Visual C++诞生到现在,它似乎丝毫没有使用Property的意思,虽然Visual C++这个名字在很大程序上沾了它的长兄Visual Basic的光,不过它并没有从VB那里学到如何让编程更简单和更轻松的秘诀。
有人可能会说,data member of class不是property吗?不是的,如果你用过C++Builder的话你一定能明白这种分别。MFC从来就不支持Property,而且今后看来也不会了,这意味着用MFC,你还是得干苦力活。(ActiveX?不错,ActiveX支持Property,而且MFC支持ActiveX开发,不过这并不是三段论发挥作用的地方。)
对MFC的批判之二:单调的处理方式使本来应该简单的工作变得复杂
应该没有人反对这样的观点:用Visual C++开发界面,特别是不符合Microsoft所谓“标准”的程序,比VB,Delphi或是C++Builder都要慢得多。(附带说一句,我不知道 Microsoft制定Windows Logo标准,并且要求程序员遵守的依据是什么;我自己的程序99%以上都不符合这个标准。)可这是为什么呢?是C++语言在这方面的天生不足?肯定不是。
在我看来,罪魁祸首是MFC中的CDocTemplate,这个类规定了一种非常死板和机械的机制,即一个Document,一个View和一个FrameWnd绑定在一起。遗憾的是,实际情况往往复杂的多。对界面稍微稍微要求高一点的程序大多要求一个Document有多个View,甚至在某些程序中,希望在同一个View中显示多个Document的内容:比如,将两个公司的业绩放在一起作比较。对View和FrameWnd的关系也有类似的情况。然而,DocTemplate的机制使得这样并不高的要求变的相当困难。想实现你的要求吗?可以。你要添加新的View类。你要从默认的 IDR_MAINFRAME复制资源到新的类中。你要用AddDocTemplate添加自定义模板。你要用
GetFirstDocTemplatePosition和GetNextDocTemplate检索模板列表。你要用GetDocString察看每个模板是否符合你的要求。你要重载CFrameWnd::OnCreateClient以派生新的视图。你要用CView::SetDlgCtrlID和 CFrameWnd::SetActiveView以及CFrameWnd::RecalcLayout来在各个视图中切换。你要用未公开的 CDocManager管理文档模板。你要...还要吗?反正我是怕了。
对MFC的批判之三:固步自封,不思进取
MFC 可以作为固步自封的活教材。别忘了,MFC是和Borland OWL同一时代的产物(还有多少人记得OWL呢?)当然,这不是MFC的错误。不过,如果以个类库的体系自从2.0版本以来就没有丝毫改变,是不是意味着这个类库已经臻于完美了呢?不,即使Microsoft也不敢这么狂妄。但事实是,MFC的体系从MFC2.0以来就没有变动过。每个版本的更新,不过是增加了一些新类,某些类的接口稍作修改,仅此而已。不,不要把ATL作为MFC的改进;ATL从来就不依赖于MFC。
谁都知道在这几年中C++语言有了多么大的改进。包括RTTI,Dynamic Creation,Exception,Standard Template Library等等都成为新的C++标准的一部分。不过,Microsoft好像并不喜欢这些新东西,它的做法是另起炉灶;于是在同以套Visual C++中就出现了两套互不兼容的实现。平心而论,在新的C++标准出台前,Microsoft自己实现这些机制实在是一个相当了不起的创举;但是历史总是在发展的,MFC为什么不从善如流,尽量利用语言中已经实现的功能,而非要固执的用自己的一套老办法?事实上,MFC几乎没有用到新的C++方案中任何有益的元素--尽管这些方案已经对MFC库中很多问题提出了相当完美而且精练的解决方法。
对MFC的批判之四:天然的倾向性
不知道您对AppWizard生成的默认项目有什么感觉。反正我的感觉是:这种工程就是用来开发象Word,Excel这样的程序的。好像MFC天然的就倾向于这样一种程序。但事实上,这种程序少之又少。
在Microsoft 看来,好像每个程序都应该有一个File菜单,而且这个菜单下面一定应该有New,Open,Save,Exit这些选项。在我的实际经验来说,我只搞过一个程序符合这样的要求。有多少人真的要搞一个自己的字处理程序或是电子表格呢?对于很多常见的、基于数据库的程序,你需要New,Open和Save 吗?如果是基于网络的程序呢?特别是在多媒体程序和游戏程序中,MFC生成的框架与其说是帮助,不如说是累赘。
这倒是符合Microsoft的一贯风格:你要的只是特定的功能,它却一并塞给你一大串不相干的东西,并且在很多时候,这些不相干的东西反而成为麻烦制造者。于是,我不得不在新生成一个项目后,不辞劳苦的去掉AppWizard“慷慨”的赠送品,包括大量无用的菜单和工具条按钮,然后才能开始实际的工作。说实在的,AppWizard在为我减少工作量的同时,也增加了我不少劳动量。
MFC定义了一个Document-View框架,并且把Document定义的相当宽泛,几乎可以代表任何数据。但是在实现上,Document是相当狭窄的;比如Document定义的Serialize固定的与一个CArchive对象联系在一起,而CArchive又固定的与一个CFile联系在一起,这样实际上就限定Document处理的对象只能是磁盘文件。况且,在一个 Serialize中处理所有数据的序列化也是一种相当机械而死板的机制:它只能处理小量而且是一次处理完毕的数据,而实际上,程序往往要处理大块的数据,并且不可能一次完成。在这种情况下,CDocument的处理机制反而是个障碍。此外,在很多类型的程序中,CDocument扮演的是一个很尴尬的角色:比如在绝大部分数据库程序中,CDocument完全是个鸡肋,实际的数据处理只能靠CRecordset来完成;再比如说,在最典型的Doc- View程序--就是记事本程序--中,CDocument根本是个无能的东西,因为它存取数据反而要求助于CEditView:: SerializeRaw。
Doc-View框架有可能突破这些限制吗?完全可能。不过,你要有心理准备,如果你要这么作,你就必须求助于一大堆的未公开函数和类型,这些东西完全没有文档,依赖于你对MFC“底下的东西”究竟有多么熟悉,以及你是否愿意钻研MFC的源代码--在绝大多数情况下这不是一件愉快的工作。如果你使用这些未公开的东西出了问题,或者你不知道如何使用,对不起,Microsoft不会给你任何支持--谁教你不按照 Microsoft的逻辑工作来着!
对MFC的批判之五:什么是封装?
好像这不成为一个问题。但是对MFC而言,封装的定义是不成立的。这句话的意思是说,如果你不想生产玩具的话,如果你需要调试程序的话,如果你的代码需要有比 AppWizard更多的东西,那么,MFC对你来说就不是封装的。如果你的程序运行出了错,对不起,问题不在你的代码里面,而在MFC库的某个文件中, Visual C++会为你打开这个文件,至于“为什么会出错?”“这个函数究竟是用来干什么的?”MFC不会给你答案,你自求多福吧。如果你除了MSDN中的公开文档以外,对MFC的源代码从不关心,那么恭喜你,你是个快乐的程序员,但是你永远不能生产出真正有用的程序。
候捷先生在其著作《深入浅出MFC》中也曾提到:或许有人会产生疑问,追寻“黑盒子底下的东西”,岂不有违面向对象编程的初衷?但是没有办法,不去熟悉MFC的源代码,永远只能生产玩具。候先生说的很委婉,尽量不批评MFC。但是,我可以这样说:使用VB,Delphi,C++Builder,Java这些语言的程序员几乎从来不去关心类库的源代码,可是这些语言并不是用来生产玩具的!
我最擅长从零开始创造世界,所以从来不怕失败,它最多也就让我一无所有。