一个月学会VC++2010 4.细说对象之香艳旖旎
作为程序员,我们其实生活在一个荒诞的空间。每次看到白发苍苍的教授,很认真很庄重的说:“面向对象灰常灰常重要”的时候,我都完全忍不住要笑出来。我们用所有的词,第一个反映怕是从字面上来理解。对象,准备结婚的异性,面,你的脸,向,朝着她。你的脸朝着异性,接下来会发生什么,外交部曰:“那是他们的内政,各国无权干涉”。教授究竟在教我们什么?
所以在这个众牛奔腾的圈子里,好象每个人都生活在这样香艳的语境中。随随便便拉头牛过来,他估计能够告诉你面向对象是多么的重要、多么的牛,你不会面向对象你都不好意思摸着键盘。你要问他什么是面向对象,他会告诉你:“嗯,不是写个类就完了,这是一种思想,一种……”,然后还是归于那三个指头、五个字,“博大精深”。
台湾对object的翻译有些不同,相对来说更合理一些:“物件”,面向对象被翻译成“物件导向”。世易时移,这两片土地的语言都在渐渐分化,作为反面角色的我们,早已经成为文化的沙漠和马某人的余孽。那是题外话,说正题。如果用今天的汉语,准确的翻译,我猜测只有一个词:“东西”,“面向对象”不妨翻译成“站在东西的角度”。
汉语原本是比较模糊的语言,生造一些词汇,或者给一些词汇赋予完全不一样的含义,这是最糟糕的情形。之所以最糟糕,因为我一向所强调的“简单”,首当其冲的就是“理解的简单”。站在东西的角度,这个小学生都能够理解,而面向对象,其他专业的博士生都无法理解。之所以会出现这类翻译,是因为语言和技术都精通,而且具备专业翻译技巧的人,几乎没有。最明显的例子,大家可以看看微软的Team Fundation Server,里面的ms agile模版,其中优先级,翻译成“堆栈级别”;然后“产品积压工作”、“用户情景”、“迭代”、“情景点”,看看,哪里象是正常人说的话?
面向对象,也令我想起一句广为流传的小资名言:“面朝大海,春暖花开”。当然你略微动动脑子,这实际是个病句。面朝大海的时候,你看到的往往是一片蓝色,如果你的身体这时候感觉到温暖,倒也正常,同一瞬间你的思维居然意识到是在春天,这就是不专心了----比较恐怖的是,你的眼睛居然在海面上发现了鲜花正在开放,唉,一段时间好象不说这句话都算是没素质,所以断章取义是不行的,这是海子的一首很平常的诗,这句话前面还有一句是“我有一所房子”,当然,是这房子面朝大海,是这房子春暖花开。
然后第一句是:“从明天起,做一个幸福的人”,两个月之后,诗人卧轨自杀,成就了杯具般的凄美。我一直不太敢说:“从明天起”如何如何,也知道幸福的人不是你想做就能做的。必须给自己保证,做一个活人,哪怕是垃圾般生存,这个比幸福重要。
我们可以回忆一下,所有教科书,都将“面向对象”概念和如何用面向对象的方式设计割裂开来,讲概念通常是一章的内容,特别精细的还分成多章,将封装、继承、多态拿出来逐一讲解。通过漫长的岁月之后,恐怕很多人还是不理解,为什么?黑板、讲解、瞌睡的三部曲而已讲解面向对象的概念,仿佛是入门的东西,讲解面向对象的思考方法,则是需求分析、设计模式等等看起来高端的范畴。
其实,还是那句话,哪里有那么复杂?我国专业人士,基本上个个都是老中医,仿佛不磨你几十年你就能够做好事情,那就谈不上博大精深。
我们就拿他们常见的场景,用我们的语言来剖析一下。假设我们要做一个模拟的生物世界游戏,有树、草、花、苹果树,有鸟、有鱼、也有狗狗和牛牛。
那么我们看到了什么?就是这些“东西”,嗯,他们说的对象:树、草、鸟、鱼等等。
他们说的继承是什么?这些东西是有分类的,分类是通过相同的特性、相同的行为来划定的。分类的原因是什么?不要重复。这里首先是生物类,哪些特性?生或者死、寿命。哪些行为,比如“生长”。然后动物和植物类,可以继承自生物类,而树可以继承自植物类,鱼可以继承自动物类。
这样,共同的特性和行为,往往不需要每个类都要重复编写。看清了吗?实际上来源于一种树形分类的方式,目的是令相同的代码不要重复编写,是一种提高效率的手段。
类对对象的区别:我们用一类东西和一群东西来表达,比如一种叫做鱼的动物,我们用class 鱼{}来定义,实际上是抽象所有鱼的特性和行为。游戏中要呈现三头鱼,两条鱼合作生下了很多鱼籽,然后又产生很多小鱼,那就是一群鱼,什么意思的:“创建了具体的鱼”,这就是所谓的实例化,常常有人鼻孔朝天的说:“你知道对象和类的区别吗?”,就象说:“你知道VC和C++的区别吗?”一样,其实是普通初中生都能够轻易理解的东西。
再说说封装:如果我们着手写这个游戏,早期的方法,显然是写一大堆的函数,会有“鱼游动”、“狗跑动”,然后一大堆数组,描述存在多个各种类型的动物、植物。这时候程序员象上帝一样,每一个细节都由他的代码所控制----这意味着他独自面对复杂的世界,编程是不是很麻烦?现在,我们将这些东西找出来,对于鱼、狗狗,他们的特性和行为都看成是一类东西,也就是类。一个名字:狗狗;一堆特性、一堆行为,这样我们的代码,就将和狗狗相关的一切包裹在一起。程序员就能站在更高层面思考问题:比如他可以先不实现这些类,这个场景需要N只狗狗、两头牛牛,创建相应数量的实例就行了,他们的移动、生长、繁殖不需要在这个层面考虑。
所以封装首先是一种良好的代码组织方式,能够简化复杂性、有利于团队合作:比如游戏中鱼居然飞起来了,去找写鱼这个东西的程序员;狗狗竟然不会动了,去找写狗狗的程序员;牛居然只有3厘米高,嗯,去找博客园的牛牛们。
至于多态,则是一种弥补方式,树形分类的思维,也就是划分层次的思维,虽然效能要高过线性思维,但也存在问题:比如动物有“移动”的行为,但鸟在飞、牛在爬、狗在跳,行为的方式不同。那么,我们只约定动物这“类”东西有个虚方法“移动”,让它从一个点到另一个点,而鸟可以重写这个需方法,可以既保持动物类在不同点之间的移动过程,又增加自己移动的姿态,方便界面上呈现出来。
大体上就这么些内容,面对任何问题,找出里面所有的东西,归纳这些东西具备的共同特征和行为,在抽象的层面寻求继承、多态从而从整体上减少代码量的可能。这就是面向对象的思考方法和实际的编程方法。东西不一定是业务上的,比如“线程”也是一种东西,操作系统范畴的。
那么针对导入日线文件这项功能,我们站在东西的角度,怎样做?
首先,我们知道要做什么:用户从其它行情软件下载日线数据;用户选择这些日线数据;系统识别这些日线数据;系统将这些日线数据保存在自己的数据库中。
这是主要的流程。
另外:因为存在对这些日线数据进行预处理的工作,用户要求只能增加系统中最新交易日之后的数据,这就存在着当某日没有;因为数据量大,导入过程等待的时间比较长,而且界面僵死不能做其他事情,因此需要随时告知导入进度,同时使用后台线程导入。因为用户选择文件很可能有误操作选中了不是我们采纳格式的文件,因此需要有文件格式的合法性检查。因为用户对行情数据的利用有大量查询要求,因此要建立相应的索引以突出查询性能,但这与导入性能冲突,因此要做好权衡,并比较禁止索引--导入--重建索引和直接导入之间的性能差异。
那么上面就是这项功能的主要描述,请注意我称它为“功能”,这是人话,不是“用例”、“用户场景”、“故事”。
我们首先看看,这里面有哪些类型的东西?
纯业务角度:用户、其他行情软件、行情文件、日线、数据库。其中没有对用户和其他行情软件的要求。
非业务角度:线程。
因此我们将用三个类来体现业务因素:日线类Quote,表达一只股票一天的日线,行情文件类QuoteFile,识别行情文件格式、转换成日线的集合,数据库类QuoteDB,对应数据库的日线表格。至于非业务对象,线程,则编程语言已经提供了足够的支持。
其他如对话框、界面设计是另一个范畴的东西,当然,你看到的仍然是一系列非业务类型的类和对象。有时候我觉得又好气又好笑,不知怎么,那么多人觉得MFC是怎样的复杂、不花费几十年读它的源代码则不足以平民愤,那就是菜鸟、那就不能做500万行代码的程序。我真无法想象500万行的VC程序是干什么的,但是,站在所谓面向对象的立场,微软这个MFC不就是已经设计好的一串类吗,它们组合起来帮助你完成日常的界面呈现、互动工作。你一定要阅读其源代码,那就是不尊重微软封装的能力、质疑微软做的产品的可用性和简单性了。相对来说:你简直是在说MFC是一群菜鸟玩出来的,连最起码的封装都显得滥竽充数。在团队中,你会仔细阅读其他同事写好的类吗?对MFC,你也可以理解成其他同事写好的类,而且是文档非常丰富的、远超出我们那种应付方式的类库,还是克制一下探究细节的冲动吧。
在你开始编程之前,这些东西大约需要一个小时甚至更短的时间就能弄清楚,忽略这个步骤往往会造成整编程过程的杂乱无章,站在东西的角度,也就是面向对象的思维方式之所以重要的原因。 这一节的主题是“谁说对象不是东西?”,起因是有牛认为多数程序员缺乏面向对象思维、算法和数据结构的知识,我不太认同。事实上,实际的编程过程中,抽象类甚至接口、继承这些出现的机率是非常小的,多数的时候大家接触最多的不过是封装特性。而算法和数据结构固然重要,但也仅仅是理解的问题,只有极少数的场景需要相应的基础---而这些东西,学院里的却未必够用,多数情形下我们的程序员接触的还是应用开发。
所以我明确的反对将这三样东西提到过分的高度,生存逼迫你编程,兴趣才能让你成长。我们还在求生的过程中......