分析和设计
面向对象范型是一个全新的编程的思维方式,当你刚刚对如何创建OOP工程有了浅显的了解时,在一些分支上还存在着许多困难。但是一旦你了解了“一切皆对象”这一机制,同时你学会了用面向对象的思维思考问题,你就有能力设计出“好”的,基于OOP带来的一切优势的方案。
“方法”(通常称作“方法论”)是一系列的过程和启发,它的目的是降低编程问题的复杂度。许多OOP方法形成于面向对象编程语言产生初期。这一节将给你以下体验:当你使用方法论时能达到什么样的目标?
特别是在OOP中,方法论是许多经验积累而成的,所以在你使用某一个方法论之前,理解它所要解决的问题是至关重要的。这一观点对于C++尤为确切,因为C++(相对于C而言)是为了降低表达的复杂度而开发的。这可能会带来某些实际效应:对于那些较为复杂的方法论的需求降低了。在C++中,简单的方法论甚至能解决更复杂的问题。而这在面向过程语言中显得捉襟见肘。
“方法论”这个术语常常显得太“大”了,似乎承诺了它力所不能及的事情,意识到这一点同样是很重要的。你在设计和编写一个程序是所使用的都是一个方法。它可能是你自己的方法,你也许在不经意之间就利用了方法,但是这是你完成你的创造性性工作的毕经的过程。如果这一过程是高效的,它可能仅需要在用C++编程时进行很少量的调整。如果你的设计没有达到最优的生产率,不能够较好的指导编程过程,这时候你可能需要考虑适应一个一般化的方法,或者在诸多一般化方法中选出一部分来应用。
当你在完成开发工作的过程中,最重要的问题是:别迷失方向。做到这一点其实很简单。许多分析和设计的方法都是为了解决那些最大最复杂的问题而设计的。请记住大多数工程并没有那么复杂,所以通常你在使用了方法中所建议的方案的相关的可用的子集时,也可以进行一次成功地分析和设计。但是一些种类的开发过程,不管它的局限性多么强,你总会发现,使用一定的方法一定会比直接写代码显得更加得心应手。
当你感觉到由于在当前阶段你没有能够解决所有的细节问题,你无法继续你的工作了,这样很容易陷入“分析瘫痪”的误区。请记住,不管在分析上你花了多大的心血,系统中都会存在一些隐秘的要素,不到设计阶段,它们是不会自己显露出来的。更多的要素会等到编码阶段才显现出来,有些要素不到程序运行时,你都不会察觉的到。正是因为如此,分析和设计的工作要尽快完成,要重视对系统的测试工作。
这一点很值得注意。因为在过去我们使用的是面向过程语言,一个好的团队在完成分析并且转向设计和实现之前,对于每一个细节都会有完整地理解。比如说,在设计一个DBMS时,完整地理解用户的需求是很重要的。但是DBMS中存在的问题在以前已经被提出并且得到了很好的解决,在很多类似的程序中,数据库的结构是需要解决的问题所在。我给在本章中所讨论的一类程序设计问题起了个名字:“通配符(wild-card)”(其实就是未知的外部因素,通配符就具有这种特征:它可以代替任何字符,但你不知道它代替的是什么——译注),wild-card富有多样性,想用一个现有的良好的设计加以简单修改就在这里完好的运行并不是那么容易的,你需要考虑若干“wild-card因素”——在现有的设计中并没有详细阐述过的元素,某种程度上讲对这些因素的研究是必需的。在进行设计和实现之前很难对wild-card因素做出完整的分析,否则将会导致分析瘫痪的发生,因为在分析阶段你对于解决这类问题并没有掌握足够的信息。解决这类问题需要在整个开发周期中反复迭代,这需要冒险的行动(这种冒险是值得的,因为你可能正在创造,潜在的回报是不可估量的),过早的进行实现工作看上去似乎很盲目,实际上这却能降低wild-card项目的风险,因为你可以在早期发现对某一问题的特定的解决方案是否可行。产品的开发过程就是风险管理的过程。
在原来,“构建项目就是为了丢弃它”这种现象很普遍。现在有了OOP,你仍然可以丢弃项目的一部分,但是由于代码是封装在类里的,在第一次迭代过程中你将会不由自主的设计出一些有用的类,开发出一些有价值的方案,这些东西是没必要丢掉的。于是我们知道,第一轮的快速开发过程不仅对下一轮的分析设计,设计以及实现提供了大量关键信息,而且为这一迭代工作创建了代码的基础。
这就是说,如果你选择了某一包含了大量细节并为开发的许多步骤和文档做出了建议的方法论,你就很难确定在那儿停止。你要时刻记住:什么你所搜寻的东西:
1. 对象(你如何将你的工程分割成有机的整体?)
2. 对象的接口(你需要对每个对象发送什么消息?)
如果对象和接口确定下来了,你就可以编程了。出于某种原因,你可能需要更多的描述和文档,但是以上的两点是必不可少的。
我们约定,分析和设计的过程分为5个阶段,还有一个“第0阶段”,我们把确定工程采取何种结构的工作放在这里。
第0阶段:制定计划
在最开始你必须做出决定整个开发过程所做的是什么。这看上去很简单(实际上,分析和设计的每个阶段看上去都很简单)并且人们通常并没有做这一步就开始编写代码了。如果你的计划是这样的:“不管三七二十一,让我们编吧!哈哈”那么好。(有时候这么做也不是完全不可取,因为你可能对问题已经了如指掌了)我们也可以勉强说这是一个“计划”。
在这一阶段,你还必须确定一些额外的处理结构,但是并不是说所有的。一些程序员喜欢像度假一样的工作,他们喜欢毫无计划的工作,不关心结构而直接进行开发;“工作做完了,结构也就出来了。”这样做看上去很酷,但是我发现把工作划分成块为每一小块设定一个目标,要比一个单一的目标“完成这一工程”来的轻松愉快的多,我们可以更加合理的安排我们自己的时间和精力。同时,把工程分成易于独立解决的小块,可以使得你的工作变得有条不紊,分得越仔细效果越明显。
曾经有一段时间我研究过故事的结构(那时候我幻想着有一天我能写一本小说),那时候我并不喜欢将故事结构化,我觉得随心所欲的写就好。后来在我写计算机书籍时我发现计算机文章本身的结构性就很完整了,因此才不需要考虑过多。但是这种结构性仍然若隐若现的在我脑海中划过。所以我建议如果你的工作仅仅是写代码,你也要尽量去经历一下下面描述的各个分析和设计的阶段。
任务描述
你所构建的所有系统,不管它多么的复杂,都会试图寻找同样的最基本的目标:所涉及问题的领域以及所应满足的基本需求。如果你能够抛开一些细节,诸如用户界面,软件硬件细节,算法,效率问题等等暂时不考虑,你将会清晰而直接地发现问题的核心所在,就像好莱坞大片里所谓的“high concept”(本人认为这个词应该直接保留。这个词是Michael Eisner说电影和电视剧的词,它的作用是用一两句话来高度浓缩剧情,没有很好的翻译方式,电影业界较为通用的说法是“剧情梗概”,类似的词汇还有“high touch”——译注),它的作用是用一两句话来概括性的描述剧情。这种纯粹的描述便是对电影精彩内容的浓缩。
High concept对于你的项目来说是至关重要的,因为它确定了项目的基调,是“对任务的描述”。其实在一开始你并不需要期盼这一任务能够百分之百的正确完成(你可以在项目的以后的阶段中,在整体还没有完成之前确立它。)但是对其进行的完善工作要始终不渝的进行。举个例子说,在一个空中运输控制系统中你可以将high concept锁定在你正在构建的系统上,像这样:“监视程序——空中交警”。但是设想一下当你把系统缩小到很小时会发生什么呢,这里可能只有一个人工控制台,甚至什么也没有。一个更实用的模型关心的是他所描述的问题,比如:“飞机到达,卸货,检修,重新装载,离港。”而不是解决方案。
第1阶段:分析(我们在做什么)
上一代程序设计(基于过程的设计)中的这一阶段称为进行“需求分析”和书写“系统说明书”。尽管它们的初衷是好的,但是这却成了令人头疼的地方,编写这些连名字都吓人的文档本身就是一项大工程了。需求分析所做的是“列出一个指南,其中指出了什么样的项目能让客户满意。”系统说明书的主要内容是:“满足需求所需要做的事情(而不是如何做这些事情)”需求分析实际上是你和你的客户之间订立的实在的合同。(有的时候,客户有可能是你公司内部的人员,甚至是其他的一些对象,或系统)系统说明书是对问题的总揽,从某种意义上讲是对项目可行性的分析以及对项目开发周期的预计。上述两者需要在你的团队中达成一致,(这是因为随着时间的推移,它们经常会改变)我认为两者越少越好,这样能节约更多时间,最理想的状况是化成列表和基本的图表。可能是出于某种原因你不得不扩大文档的规模,但是最起码应该保证最初的文档小而简洁。可以设立若干小组会议集思广益,这一过程中小组的负责人就可以完成分析文档。这样做不仅征求了大家的意见,而且促使团队达成一致意见。同时可以激发整个团队的开发热情,这或许才是最重要的。
在这一阶段,把注意力放在“我要完成的是什么”上是很重要的:你要确定下来系统的功能究竟是什么。在这一阶段最有价值的工具是“用例(use case)”。用例是用来确定系统的关键特征的,它们可以揭示出一些你将会使用到的一些基础的类。用例是对下面列出的问题的描述性解答:
“谁会使用这个系统?”
“使用这一系统,参与者能做些什么?”
“特定的参与者如何做这件事?”
“如果其它的参与者试图做这件事,或者同一参与者试图作某些其它的事,系统如何运作?”(从而发现系统变化情况)
“系统运行这些事时会发生什么问题?”(用以发现异常)
比如说你正在设计一个自动提款机,对于系统功能进行详细说明的用例应该可以描述出自动提款机在所有可能的情况下作出的行为。每一种“情况”被称为一个“场景”,一个用例可以看成是一组场景的集合。你可以把场景想象成为这样的一类问题“如果发生了某某事,系统将做些什么?”比如说,“如果一个客户在24小时内存入一张支票,这张支票尚未过户,但此时账目中没有足够的余款可供提取,这个时候自动提款机将做何种处理?”
用例图有意设计得非常简单,这可以防止你过早的陷入考虑系统实现细节的泥沼中。
每个小人表示一个“参与者”,参与者可能是一个人,也可能是其它的什么东西。(甚至有可能是其他的计算机系统,比如在上边的用例图中的“ATM”)方框表示系统边界。椭圆表示用例,就是对系统各个功能的描述。参与者与用例之间的连线表示他们之间的交互的关系。
对于用户来说,系统如何实现并不重要。
一个用例并不需要有多复杂,即使底层系统非常复杂也是一样。用例只是用来表示用户看上去的系统是什么样的,比如说:
用例可以通过确定用户与系统之间交互的内容来进行需求说明。
阶段2:设计(我们如何构建它)
面对项设计的五个步骤
1.搜寻可用对象
2.组装对象
3.构件系统
4.扩展系统
5.对象复用
对对象开发过程的几点建议: