面向对象的范式是思考程序设计时一种新的、而且全然不同的方式,许多人最开始都会在如何构造一个项目上皱起了眉头。事实上,我们可以作出一个“好”的设计,它能充分利用OOP提供的所有优点。
有关OOP分析与设计的书籍大多数都不尽如人意。其中的大多数书都充斥着莫名其妙的话语、笨拙的笔调以及许多听起来似乎很重要的声明(见注释)。我认为这种书最好压缩到一章左右的空间,至多写成一本非常薄的书。具有讽剌意味的是,那些特别专注于复杂事物管理的人往往在写一些浅显、明白的书上面大费周章!如果不能说得简单和直接,一定没多少人喜欢看这方面的内容。毕竟,OOP的全部宗旨就是让软件开发的过程变得更加容易。尽管这可能影响了那些喜欢解决复杂问题的人的生计,但为什么不从一开始就把事情弄得简单些呢?因此,希望我能从开始就为大家打下一个良好的基础,尽可能用几个段落来说清楚分析与设计的问题。
[注释]:最好的入门书仍然是Grady Booch的《Object-Oriented Design withApplications,第2版本》,Wiely & Sons于1996年出版。这本书讲得很有深度,而且通俗易懂,尽管他的记号方法对大多数设计来说都显得不必要地复杂。
1 不要迷失 在整个开发过程中,最重要的事情就是:不要将自己迷失!但事实上这种事情很容易发生。大多数方法都设计用来解决最大范围内的问题。当然,也存在一些特别困难的项目,需要作者付出更为艰辛的努力,或者付出更大的代价。但是,大多数项目都是比较“常规”的,所以一般都能作出成功的分析与设计,而且只需用到推荐的一小部分方法。但无论多么有限,某些形式的处理总是有益的,这可使整个项目的开发更加容易,总比直接了当开始编码好!
也就是说,假如你正在考察一种特殊的方法,其中包含了大量细节,并推荐了许多步骤和文档,那么仍然很难正确判断自己该在何时停止。时刻提醒自己注意以下几个问题:
(1) 对象是什么?(怎样将自己的项目分割成一系列单独的组件?)
(2) 它们的接口是什么?(需要将什么消息发给每一个对象?)
在确定了对象和它们的接口后,便可着手编写一个程序。出于对多方面原因的考虑,可能还需要比这更多的说明及文档,但要求掌握的资料绝对不能比这还少。
整个过程可划分为四个阶段,阶段0刚刚开始采用某些形式的结构。
2 阶段0:拟出一个计划 第一步是决定在后面的过程中采取哪些步骤。这听起来似乎很简单(事实上,我们这儿说的一切都似乎很简单),但很常见的一种情况是:有些人甚至没有进入阶段1,便忙忙慌慌地开始编写代码。如果你的计划本来就是“直接开始开始编码”,那样做当然也无可非议(若对自己要解决的问题已有很透彻的理解,便可考虑那样做)。但最低程度也应同意自己该有个计划。
在这个阶段,可能要决定一些必要的附加处理结构。但非常不幸,有些程序员写程序时喜欢随心所欲,他们认为“该完成的时候自然会完成”。这样做刚开始可能不会有什么问题,但我觉得假如能在整个过程中设置几个标志,或者“路标”,将更有益于你集中注意力。这恐怕比单纯地为了“完成工作”而工作好得多。至少,在达到了一个又一个的目标,经过了一个接一个的路标以后,可对自己的进度有清晰的把握,干劲也会相应地提高,不会产生“路遥漫漫无期”的感觉。
座我刚开始学习故事结构起(我想有一天能写本小说出来),就一直坚持这种做法,感觉就象简单地让文字“流”到纸上。在我写与计算机有关的东西时,发现结构要比小说简单得多,所以不需要考虑太多这方面的问题。但我仍然制订了整个写作的结构,使自己对要写什么做到心中有数。因此,即使你的计划就是直接开始写程序,仍然需要经历以下的阶段,同时向自己提出一些特定的问题。
3 阶段1:要制作什么? 在上一代程序设计中(即“过程化或程序化设计”),这个阶段称为“建立需求分析和系统规格”。当然,那些操作今天已经不再需要了,或者至少改换了形式。大量令人头痛的文档资料已成为历史。但当时的初衷是好的。需求分析的意思是“建立一系列规则,根据它判断任务什么时候完成,以及客户怎样才能满意”。系统规格则表示“这里是一些具体的说明,让你知道程序需要做什么(而不是怎样做)才能满足要求”。需求分析实际就是你和客户之间的一份合约(即使客户就在本公司内部工作,或者是其他对象及系统)。系统规格是对所面临问题的最高级别的一种揭示,我们依据它判断任务是否完成,以及需要花多长的时间。由于这些都需要取得参与者的一致同意,所以我建议尽可能地简化它们——最好采用列表和基本图表的形式——以节省时间。可能还会面临另一些限制,需要把它们扩充成为更大的文档。
我们特别要注意将重点放在这一阶段的核心问题上,不要纠缠于细枝末节。这个核心问题就是:决定采用什么系统。对这个问题,最有价值的工具就是一个名为“使用条件”的集合。对那些采用“假如……,系统该怎样做?”形式的问题,这便是最有说服力的回答。例如,“假如客户需要提取一张现金支票,但当时又没有这么多的现金储备,那么自动取款机该怎样反应?”对这个问题,“使用条件”可以指示自动取款机在那种“条件”下的正确操作。
应尽可能总结出自己系统的一套完整的“使用条件”或者“应用场合”。一旦完成这个工作,就相当于摸清了想让系统完成的核心任务。由于将重点放在“使用条件”上,一个很好的效果就是它们总能让你放精力放在最关键的东西上,并防止自己分心于对完成任务关系不大的其他事情上面。也就是说,只要掌握了一套完整的“使用条件”,就可以对自己的系统作出清晰的描述,并转移到下一个阶段。在这一阶段,也有可能无法完全掌握系统日后的各种应用场合,但这也没有关系。只要肯花时间,所有问题都会自然而然暴露出来。不要过份在意系统规格的“完美”,否则也容易产生挫败感和焦燥情绪。
在这一阶段,最好用几个简单的段落对自己的系统作出描述,然后围绕它们再进行扩充,添加一些“名词”和“动词”。“名词”自然成为对象,而“动词”自然成为要整合到对象接口中的“方法”。只要亲自试着做一做,就会发现这是多么有用的一个工具;有些时候,它能帮助你完成绝大多数的工作。
尽管仍处在初级阶段,但这时的一些日程安排也可能会非常管用。我们现在对自己要构建的东西应该有了一个较全面的认识,所以可能已经感觉到了它大概会花多长的时间来完成。此时要考虑多方面的因素:如果估计出一个较长的日程,那么公司也许决定不再继续下去;或者一名主管已经估算出了这个项目要花多长的时间,并会试着影响你的估计。但无论如何,最好从一开始就草拟出一份“诚实”的时间表,以后再进行一些暂时难以作出的决策。目前有许多技术可帮助我们计算出准确的日程安排(就象那些预测股票市场起落的技术),但通常最好的方法还是依赖自己的经验和直觉(不要忘记,直觉也要建立在经验上)。感觉一下大概需要花多长的时间,然后将这个时间加倍,再加上10%。你的感觉可能是正确的;“也许”能在那个时间里完成。但“加倍”使那个时间更加充裕,“10%”的时间则用于进行最后的推敲和深化。但同时也要对此向上级主管作出适当的解释,无论对方有什么抱怨和修改,只要明确地告诉他们:这样的一个日程安排,只是我的一个估计!
4 阶段2:如何构建? 在这一阶段,必须拿出一套设计方案,并解释其中包含的各类对象在外观上是什么样子,以及相互间是如何沟通的。此时可考虑采用一种特殊的图表工具:“统一建模语言”(UML)。请到http://www.rational.com去下载一份UML规格书。作为第1阶段中的描述工具,UML也是很有帮助的。此外,还可用它在第2阶段中处理一些图表(如流程图)。当然并非一定要使用UML,但它对你会很有帮助,特别是在希望描绘一张详尽的图表,让许多人在一起研究的时候。除UML外,还可选择对对象以及它们的接口进行文字化描述(就象我在《Thinking in C++》里说的那样,但这种方法非常原始,发挥的作用亦较有限。
我曾有一次非常成功的咨询经历,那时涉及到一小组人的初始设计。他们以前还没有构建过OOP(面向对象程序设计)项目,将对象画在白板上面。我们谈到各对象相互间该如何沟通(通信),并删除了其中的一部分,以及替换了另一部分对象。这个小组(他们知道这个项目的目的是什么)实际上已经制订出了设计方案;他们自己“拥有”了设计,而不是让设计自然而然地显露出来。我在那里做的事情就是对设计进行指导,提出一些适当的问题,尝试作出一些假设,并从小组中得到反馈,以便修改那些假设。这个过程中最美妙的事情就是整个小组并不是通过学习一些抽象的例子来进行面向对象的设计,而是通过实践一个真正的设计来掌握OOP的窍门,而那个设计正是他们当时手上的工作!
作出了对对象以及它们的接口的说明后,就完成了第2阶段的工作。当然,这些工作可能并不完全。有些工作可能要等到进入阶段3才能得知。但这已经足够了。我们真正需要关心的是最终找出所有的对象。能早些发现当然好,但OOP提供了足够完美的结构,以后再找出它们也不迟。
5 阶段3:开始创建 你可能是程序员,现在进入的正是你可能最感兴趣的阶段。由于手头上有一个计划——无论它有多么简要,而且在正式编码前掌握了正确的设计结构,所以会发现接下去的工作比一开始就埋头写程序要简单得多。而这正是我们想达到的目的。让代码做到我们想做的事情,这是所有程序项目最终的目标。但切不要急功冒进,否则只有得不偿失。根据我的经验,最后先拿出一套较为全面的方案,使其尽可能设想周全,能满足尽可能多的要求。给我的感觉,编程更象一门艺术,不能只是作为技术活来看待。所有付出最终都会得到回报。作为真正的程序员,这并非可有可无的一种素质。全面的