Ood启思录01
- 1. 第1章
面向对象编程的动因
1.1 革命家、改革家与面向对象范型
在学习面向对象范型以及相关知识的过程中,你首先必须知道我们社区中
的很多对立观点。每组对立观点意味着两个或者多个阵营,他们对自己的观点
一般都具有宗教般的热情。最重要的对立观点之一是革命家与改革家之争。革
命家相信,有一群开发者某一天在凌晨 3 点醒来,并发现以前我们一直都在用
错误的方式开发软件。他们相信,他们找到了解决软件危机的方法,并且把这
种方法叫做“面向对象编程”。或许读者已经猜到,我是属于改革家阵营的。
改革家认为,面向对象编程不是软件开发领域的革命,而是使得开发者远
离底层机器细节的持久进步中顺其自然的一步。软件危机的来源并不是我们在
用错误的方法开发软件,而是因为我们的编程范型已经不足以应付我们所需解
决问题的日益增加的复杂性了。 在处于只有 8 条指令和 1KB 内存(而且这 1KB
中 90%还被操作系统占据了)的年代,用八进制或者十六进制操作码来写程序
是天经地义的。我使用这种编程方法毫无障碍,因为只要记 8 条指令,要写的
代码也不过 30 行。而当硬件变得更加强健,我开始遇到需要 64KB 内存的问题,
这样一来记忆操作码就变得复杂,超出了我的掌控能力。于是,我开始使用汇
编语言助记符。助记符范型非常完美,直到有一天我的问题再次超越它能容忍
的复杂性上限。于是我改用高级语言来编程。高级语言的每条指令都能替换很
多条汇编语言助记符,这样我就能在更高层次上思考。除了减少语言复杂性,
我也在寻找思考问题的更好方式。我不是把一串串的指令塞进小的函数再把这
- 2. 2 第1章 面向对象编程的动因
些函数拼成程序,而是用一种结构化的方法来把问题分割成小的子问题,每个子问题的复杂
性都在可管理的范围之内。
目前,软件开发的发展又到达了临界点。对于今天的硬件所能处理的问题的复杂性,结
构化的、自顶向下的设计方法已经不足以应付了。我们需要找到一种软件开发范型,它要可
以应付增加的复杂性。几年以前,菜单驱动的系统还广为使用;今天的软件系统已经必须具
有图形用户界面,否则在市场上会乏人问津。在未来的几年,消费者会期望可以与他们对话
并显示动态视频的多媒体应用程序。作为一种应付更复杂的应用程序的方法,面向对象范型
为什么会引起那么多关注呢?
最主要的原因之一是,迁移到面向对象范型可以让开发者以更接近现实世界的模型来开
发复杂的应用程序。在日常生活中,什么是大家公认的特别复杂的东西呢?我们发现,很多
以分布的方式彼此交互的机器构成了复杂的系统。并没有中央控制机制来要求所有成员都汇
报工作、听取命令、完成命令并回来等着分配更多的工作。结构化方法是按照中央控制的思
路设计的。而分布构架的优点在于,任何问题都只会影响应用软件的一个部分。在好的设计
中,这个部分和其他部分应该是松耦合的。这样,如果系统的一个部分出了问题,或者我们
想扩展该部分,改变所带来的影响就被局限在那个部分,而不会影响全局。既然分布的方式
让现实世界接受了这一切复杂性的存在,并且这些复杂的事物都得以顺利运作,那么为什么
我们不能以同样方式来应对软件问题的复杂性呢?
对象技术的初学者可能会遇到这样的建议: “当你成为一个真正的面向对象开发者之
前,你需要承受一次范型迁移(paradigm shift)”虽然这似乎有些耸人听闻,但“范型迁移”
。
的说法有其内在真理。软件开发者需要用分布的方式来思考,而不再遵照传统结构化方法的
集中控制方式。因为成功的现实世界实体是以分布的方式交互的。我认为,这一范型迁移不
仅有其学习曲线,还有其遗忘曲线。你需要忘记你以前学到的面向动作(action-oriented)型
软件开发所依赖的集中控制的方式。
关于面向对象编程,无论你支持革命家的观点还是改革家的观点,你学习面向对象范型
会遇到的第一件烦恼事就是充斥该领域的时髦词语。例如,我们在本书中要讨论的一个相当
简单的概念叫做“多态” (polymorphism),一个具有希腊语词根的单词隐藏了一个简单的概
念。而且,这个概念还有很多同义词,比如动态绑定(dynamic binding) 、运行时类型识别
1
(runtime-type discrimination )。而且还有很多用于修饰这些单词的形容词,比如“纯的”
(pure)多态、 “真正的” (true)多态。面对这些,你很容易坠入迷惘之网。在新生领域,时
髦词语是一种流行病,因为待到技术逐渐成熟至能够用标准语汇来准确表达该技术的概念,
还需要相当长的时间。在面向对象领域中,这个问题更为严重,因为计算机科学的很多不同
子领域不约而同地在向对象范型迁移,而每个子领域都有它们自己的一套术语。可能一个领
1
译注:现在更多是称作RTTI。
- 3. 1.2 Frederick Brooks 观点:非根本复杂性与根本复杂性 3
域的人很高兴地谈论包含关系和继承关系, 另一个领域的人已经把这些概念分别称作“has-a”
和“a-kind-of”关系了。我估计,在不久的将来,时髦词语的问题还会常伴我们左右,但等
到这个领域成熟了,这个问题也就不复存在了。在这个过渡时期中,我将避免在本书中使用
同义词,除非我需要向用户强调一些东西。
1.2 Frederick Brooks 观点:非根本复杂性与根本复杂性
Frederick Brooks 在 1987 年 10 月份的 IEEE Computer 上发表了一篇有趣的文章,标题是
Conceptual Essence of Software Engineering or There Is No Silver Bullet [参考文献 1]。Frederick
Brooks 是 The Mythical Man-Month [参考文献 2]的作者。The Mythical Man-Month 记录了他管
理软件开发项目的经验,包括担任 IBM360 项目经理那两年的经验,是软件工程领域的必读
书。他的书描述了他的项目中做对和做错的地方,并且解释了为什么。每个同软件生产有关
的人,特别是项目经理,都应当读一下这本书。那篇“No Silver Bullet” (没有银弹)文章是
关于他对软件工程的洞察的续篇。在该文中,他谈论了我们为什么会遇上软件危机,为什么
没有解决所有问题的万能方法,在未来我们能使用什么有潜力的技术来缓解这个危机。
该文提出的一个基本观点是,造成软件危机的有两种复杂性,一种是非根本复杂性
(accidental complexity),一种是根本复杂性(essential complexity)。非根本复杂性来自不适
合于应用软件的范型、方法学和/或工具。只要有足够的资源来创建或者购买各种互为补充的
工具,这类复杂性是可以消除的。面向对象编程有助于消除非根本复杂性,因为它提供了一
种一致的软件开发范型,包容了分析、设计、实现各个阶段。这不是说面向对象的软件项目
不包含非根本复杂性。MIS(Management Information Science,管理信息科学) 2 世界和其他
领域面对的是一种特殊类型非根本复杂性。这些组织已经把大笔资金投入到关系数据库技术
中去了,而现在正从面向动作往面向对象迁移。关系数据库模式语言的表现力不足以直接描
述面向对象世界中数据和行为间的复杂关系。结果就是,面向对象设计者需要把这些复杂的
关系翻译成关系数据库中简单的关系。这一翻译带来了非根本复杂性,而大多数MIS公司都
愿意接受这一复杂性, 因为否则的话他们就需要抛弃已经在用的久经考验的关系数据库产品,
而去购买未经严格测试的面向对象数据库。即便在这样的情况下,面向对象范型也使得我们
可以通过包装器(wrapper)来控制这种复杂性 3 。包装器是指把应用软件的一个部分同其他
部分的非根本复杂性隔离开来的抽象层。我们将在第 9 章再讨论这种包装器机制。第 9 章覆
盖了面向对象的物理设计问题。
软件危机的真正原因是根本复杂性。根本复杂性来自这一事实:软件本质上就是复杂的,
2
译注:现在更多是指管理信息系统。
3
译注:现在很多O/R Mapping产品就是这样的wrapper。
- 4. 4 第1章 面向对象编程的动因
没有哪种方法学或者工具可以消除这一复杂性。软件具有根本复杂性的理由如下:
1.从规模上来说,软件应用程序是人类创建的最复杂的实体。
2.软件是难以把握的,而且大部分是无形的。
3.软件不会像具有移动零件的机器那样会在传统意义上磨损折旧。但是,人们常常会以
软件编写者从未想到的方式来使用软件(并常常会发现错误),而且最终用户始终希
望他们的软件得以扩展。
1.3 瀑布模型
以往的范型把软件开发过程当作一条装配流水线,这已经不再正确。请看图 1.1 所示的
传统的软件开发瀑布模型。在这一模型中,分析、设计、编码、测试和维护形成了 5 个独立
的步骤,每个步骤都有精确定义的输入和精确定义的输出。每个阶段的过渡,输出工件都成
为经理评估项目进度的依据。没过多久,软件与装配流水线的不同之处就显而易见了。想象
一下,如果装配流水线上有一个环节速度太慢而拖累了其他环节,我们能采取的最佳行动是
什么?比如,如果装配流水线正在组装洋娃娃,负责安装胳膊的工人太慢了而拖累了整条流
水线,那么我们应当怎么办?显然,我们应当指派另外一位工人去协助原来安装胳膊的工人。
这样,我们就可以消除瓶颈。那么,在软件工程中也尝试一下这种方法吧,结果多半是灾难
性的。如果编码延迟了,项目经理无法简单地给开发团队增加一些人手。如果这样做的话,
团队现有成员的生产效率会降低,因为他们需要花时间来指导新来的人。但是,很多公司依
然在继续使用瀑布模型来开发项目。
图 1.1 软件开发的瀑布模型
或许瀑布模型易于理解,易于跟踪,并且符合经理人员的喜好,因为他们可以通过一系
列精确定义的工件来跟踪进度。但是,对大系统开发者而言,这个模型并不能很好工作。事
实上,我怀疑开发者是否真地用过这个模型。如果你在设计你的第 15 个邮件列表程序,这个
- 5. 1.4 迭代模型 5
模型可能会工作得很好。你已经创建过 14 个邮件列表程序了,现在你只需要稍稍修改一下你
已有的分析模型和设计模型,并实现新的模型(这个新模型看上去和以往的 14 个非常像) ,
然后测试它。但如果把一个负责折叠西装袖子的机器人实时进程控制系统的规约交给写邮件
列表的这组开发者,并要求他们用瀑布过程来写出一个好的应用程序, 这实在是无法想象的。
我有太多的开发者朋友被要求用瀑布模型来应对不熟悉的领域, 而他们的反应是:“噢,好吧,
我会写那些规约以便让老板高兴,但我会以我想要的方式来编写真正的东西。 ”当然,错误的
规约要比根本没有规约还糟。为什么不建立一个真正反映现实的软件开发过程呢?这样的过
程应当包含回头修改设计、增加新的需求以及测试新的想法等要求。这样的过程被称作软件
开发的迭代式过程。
1.4 迭代模型
软件开发的迭代模型看上去和瀑布模型差不多,区别只在于迭代模型允许开发者沿项目
流程往返(见图 1.2)。如果我们在为系统的某个部分编写代码时发现了一个设计缺陷,我们
可以回到设计阶段来分析并改正它。或者,如果我们在测试系统的一部分时发现了新的系统
需求,我们可以回到分析阶段来修正这个问题。在面向动作范型中,迭代模型会带来很多问
题。面向动作的软件常常会有很多位于数据和行为之间的隐含依赖关系。再同集中控制机制
一结合,你就会发现自己处于这样一个境地:如果触动了已经存在的应用程序的部分,整个
系统就会轰然倒塌。如果已经为应用程序编写了 90%的代码,那么增加需求或者改变设计是
无法接受的。面向对象范型改正了这一问题,它向开发者提供分布式的流程控制,并使他们
具备防止数据、行为间隐含依赖性的能力。于是,对面向对象开发者来说,软件开发的迭代
模型就成了理所当然的选择。1
图 1.2 软件开发的迭代模型
1
译注:面向对象范型是通过解耦合来使得修改系统已有部分变得容易且不会引入bug,但这还不够,所以人们提出通过重构
(refactoring)和单元测试(unit testing)使得迭代开发更容易。而在解耦合方向上的新的探索则包括AOP、MDA等。
- 6. 6 第1章 面向对象编程的动因
但是,迭代模型也不是一点问题都没有。虽然我相信,这个模型准确地反映了从系统构
架师角度上看到的开发过程,但是对项目经理来说,它却带来了很大的问题。简而言之,这
个模型目前缺乏一系列精确定义的开发里程碑。这并不意味着在整个软件系统构筑完毕移交
客户的那一天之前,项目经理无法获得任何反馈;而是表明我们需要新的迭代式的里程碑来
在应用系统成型之前就提供必要的反馈。
一种这样的工件(deliverable)是软件原型(software prototype)
。原型领域源自这样的认
知:现实世界中的复杂实体是逐渐生长而成的(grown) ,而不是创建而得的(built) 。很多开
发者都把原型看作是控制当今软件根本复杂性的方法。通过创建原型,应用程序可以一次增长
一层,每层都经过彻底的测试,然后才开始下一层的增长。通过这种方法,设计缺陷就可以尽
早发现,从而改正的代价也相对较低。而工作原型也可以用作生产率的衡量尺度。通过衡量原
型中功能点的数目并将其同伪功能点(尚未真正实现其功能)相比较,我们可以跟踪进度。 2
1.5 构造原型:相同语言与不同语言
原型领域可划分为两大流派。一派认为原型与最终产品应当用同一种语言编写
( same-language prototyping ) 另 一 派 认 为 原 型 与 最 终 产 品 应 当 用 不 同 的 语 言 编 写
,
(different-language prototyping)。相同语言原型带来的好处看起来简直无法拒绝:使用同一
种语言,这意味着同一套工具,相同的参考书,相同的学习曲线,相同的培训课程。一点也
不奇怪,这两大流派中相同语言原型被更多地运用。
最后的原型终将成为产品。这一模型的主要缺点是,在创建原型过程中,编写垃圾代码
(缺少修饰,缺乏效率、健壮性和可扩展性)是允许的(如果不说是被鼓励的话) 。毕竟,创
建原型的目的是测试设计想法和需求的可行性,而不是实现最终产品。在这个领域,很多程
序员都忍不住要这样说,“我知道这个代码缺少前面提到的那些特性,但毕竟它只是个原型,
以后我会重写应用程序的这个部分。”当然,6 个月之后,项目延期了,“以后”永远不会成
为现在,垃圾代码则随产品被交付了。
相同语言原型的另一个问题是,如果软件平台不在前端出现,语言问题有时可能会影响
设计者对系统开发的判断。例如,C++开发者常常会遇到设计问题,因为他们的语言是一种
多范型语言(multiparadigm language) ,而不是像 SmallTalk 那样的纯粹面向对象语言(pure
object-oriented language) 多范型语言允许开发者很容易地混合使用面向对象范型和面向动作
。
范型。而纯粹面向对象语言则诱使开发者使用面向对象范型。使用多范型语言来进行面向对
象开发的一个缺点是,开发者很容易倒退到直接的面向动作开发过程中去。如果开发者使用
2
译注:作者在这里只是粗略提及这样的开发方式,本书出版几年后的今天,这样的开发方式已经成为现实。
- 7. 1.6 软件复用性 7
纯粹的语言,那么他就可以确保使用面向对象开发的构造。
(请注意,不要把“面向对象”与
“好”相等同。使用纯粹语言的开发者也可能是糟糕的面向对象开发者,可能会开发出无法
维护的系统,虽然系统是用面向对象的结构来搭建的。不必惊讶于面向对象范型也允许使用
者创建糟糕的系统。没有哪种技术能防止这样的事情发生。)
在不同语言原型中,我们可以把上面讨论的优势和劣势交换一下。两条学习曲线,两套
工具等等令人生畏;此外,说服你的经理并说服你自己,编写 5 000 行代码然后把它扔进垃
圾桶是件好事,也颇具难度。我会首先争辩说,代码本身没什么意义,从代码提炼出来的无
形的设计才是真正有价值的。但是,你依然会遇到很大的心理阻力,特别是在仍旧使用编写
的代码数量来衡量生产力的公司环境中。而从不“偶然地”交付垃圾代码,可以在纯粹的面向
对象环境中工作而不必理会应用程序的目标语言, 这些优势也足以吸引很多公司采用这种创建
原型的方法。典型的语言组合是,用 SmallTalk 来创建原型,用 C++作为应用程序的目标语言。
1.6 软件复用性
控制根本复杂性的另一种方法是干脆避免开发软件。如果能买到软件,何必要创建它
呢?我们的 MIS 开发者并不创建他们的关系数据库,而是购买现成的产品。如果你需要电子
数据表格,你也不会自己创建一个,你会从 Lotus、Microsoft、Borland 或者其他的供应商购
买。购买软件的优势是要比创建软件便宜,特别是在考虑了维护成本的情况下。主要的劣势
则是,你获取的功能就是他们提供的功能。你自己创建的软件可以精确地满足你的需要,但
是买来的软件很可能并非如此。
现在很多人不使用“购买软件”这个术语,而是说“复用软件”“软件复用”在面向对。
象社群中成了一个主要的时髦词语。为什么在面向对象世界中软件复用这样振奋人心?我们
一直都在复用软件。 多少开发者在他们最喜欢的编程语言中用过 if 语句?多少 C 程序员用过
printf?多少 Pascal 程序员用过 writeln?为什么人们再次对软件复用展开讨论呢?我们知道,
代码的尺寸(或者说粒度)和它的灵活性成反比。if 语句很小,所以很灵活;而 Lotus 1-2-3
是大尺度的复用,但它的目标很专一。而面向对象范型中的那些构造在粒度和灵活性间取得
了较好的平衡。不幸的是,面向对象程序所达到的软件复用层次让很多开发者失望了。这一
失望有几个原因,我们将在本书讨论相关面向对象构造的章节中展开讨论。
1.7 优秀设计者阶层
Brooks 在“No Silver Bullet”一文中提到的作为控制根本复杂性方法的最后一个话题是
- 8. 8 第1章 面向对象编程的动因
在企业中建立一个优秀软件设计者阶层,让他们从大批初级设计者中选拔接班人。这可以同
管理者阶层类比:高级经理位居顶端,并且从大批初级经理中选拔接班人。这触及了软件开
发者间“艺术还是科学”这一争论的核心。软件开发能力是后天习得的,还是需要天赋?我
不想参加这一争论,但我想做个类比。如果有人拿枪指着我的脑袋逼我在一年内学会弹钢琴
(我之前从未学过乐器) 我不会觉得太困惑。
, 如果事关自己的生命,我当然可以学会弹钢琴。
但是,如果那个拿枪指着我的人逼我在三年内成为一个伟大的钢琴家,那我恐怕只能让他开
枪了。
不管伟大的设计者是天生的还是后天培养出来的,我认为“建立一个优秀软件设计者阶
层”这一做法有一个严重缺陷。这也是目前一些公司管理者阶层所存在的缺陷。新的想法来
自何处呢?传统上,新念头来自底层(草根阶层)的运动。若建立等级层次,那么我们就可
能迟滞不前。作为例证,我发现了有趣但也令人吃惊的事实:结构化设计和逐步细化方法很
大程度上来自学术界,而工业界则不太情愿接受;而面向对象编程技术则来自工业界和实验
室,学术界则不太情愿接受(当然也有不少例外) 。我相信,学术界不情愿向本科生讲授面向
对象编程技术是因为那些已经走上高等级层次的教授们以前一直都是在宣扬面向动作
(action-oriented)的好处。而面向对象社群声称“我们 30 年来一直在用错误的方法开发软
件,面向对象编程才是正确的方法” ,这更是加大了与学术界的隔阂。
我们现在已进入这样一个时代,软件开发的复杂性已不是结构化方法所能应付的。而随
着硬件性能呈指数增长,未来的软件只会更加复杂。我们所面临的问题是,能否发明一种软
件开发方法学,能够提供这样的可能性:消除非根本复杂性,并且至少是控制根本复杂性?
我相信,面向对象范型通过分散控制流、相互关联的数据和行为、隐含的情况分析(即多态)
和信息隐藏机制,再配合快速原型和软件开发的迭代模型,为实现这一目标提供了最佳的可
能性。1 本书的剩下部分将讨论关于面向对象范型的很多问题,并讨论如何追踪和解决这些问
题,让面向对象范型所提供的可能性更接近现实。
术语表
Accidental complexity
非根本复杂性。在应用程序中使用不合适的软件开发范型或者工具造成的复杂性。
Different-language prototyping
不同语言原型化。一种创建原型的方式,用来创建原型的语言和项目语言不同。
1
译注:从现在看来,面向对象范型还不足以解决这些问题。AOP、IP等都是新的努力。也有人把这些相关的“非面向对象”的
努力归纳为Generative Programming。OMG正主推的MDA也是基于Code Generation的技术。
- 9. 术语表 9
Essential complexity
根本复杂性。因为应用软件本身的性质造成的复杂性。
Multiparadigm language
多范型语言。既支持面向对象范型又支持面向动作范型的编程语言。
Paradigm shift
范型迁移。从旧的编程模型到新的编程模型的转移。
Programming paradigm
编程范型。开发软件的模型。
Pure object-oriented language
纯面向对象语言。只支持面向对象范型的语言。
Same-language prototyping
相同语言原型化。一种创建原型的方式,用来创建原型的语言和用来创建最终产品的语
言是一样的。
Software prototype
软件原型。应用软件的模型,用来测试软件设计、实现或者解决方案的可行性。常常为
了可以快速完成而省略了可扩展性、效率和稳定性这些特性。
Software prototyping
软件原型化。创建最终应用软件产品的模型的行为,用来测试或者证明特定软件的设计、
实现或者解决方案的可行性。
Iterative model (of software development)
(软件开发的)迭代模型。一种设计软件的灵活的模型,它意识到软件开发是一个迭代
的过程,必须允许实践者可以修改已有的成果来改正前面犯下的错误。
Waterfall model (of software development)
(软件开发的)瀑布模型。一种设计软件的不灵活的模型,它注重的是产生精确定义的
工件的里程碑,开发过程是单向的,也就是说,一旦到达一个里程碑,那么前面所做的步骤
就不能改变了。