只供参考,喜欢请支持正版图书
2.1 建模
建模的定义本身就和建模工作一样非常抽象和难以理解。为了理解,我们简单地说:建模包含两个问题,一个是怎么建,另一个是模是什么。
让我们想象一下城市里遍布的摄像机,虽然它们拍摄的都是同一座城市,但不同的机位看到的情景是不同的,每个机位都反映出了城市的一个方面。如果我们要认识这个城市,就需要先明确我们想了解城市的什么,然后选择最有代表性的机位,从各个机位采集来信息,并分析这些信息的相关性,做出逻辑解释。
城市就是我们面临的问题领域,而机位就是抽象角度。实际的需要引导我们去寻找适合的机位,从而找到适合的抽象角度。再接下来,分析工作就能顺利开展了。
道理很简单,但很实用。不论在需求分析、系统分析还是系统设计上,读者一定要学会采用面向对象的方法,在面对问题领域的时候首先不要决定去通盘考虑,而是找出问题领域里包含的抽象角度。如果你把抽象角度都找全了,并且这些角度都分析清楚了,问题领域也就解决了。虽然这些抽象角度在思考的时候可能是互不关联的。
具体来说,做需求的时候,首要目标不是要弄清楚业务是如何一步一步完成的,而是要弄清楚有多少业务的参与者?每个参与者的目标是什么?参与者的目标就是你的抽象角度。与分析一个复杂的业务流程相比,单独分析参与者的一个个目的要简单得多。实际上,这就是用例!这也就是为什么用例会成为业务建模的方法的原因之一。
第二个问题“模是什么”,则依赖于确定了抽象角度下的场景模拟。
现在回到什么是模的问题上来,一个由抽象角度确定了的目标需要由静态的事物加上特定条件下产生的一个特定的场景来完成,即静态的事物(物)+特定的条件(规则)+特定的动作(参与者的驱动)=特定的场景(事件)。
2.2 用例驱动
用例驱动是统一过程的重要概念,或者说整个软件生产过程就是用例驱动的。用例驱动软件生产过程是非常有道理的。让我们再次回顾建模公式,很容易得出一个推论,要解决问题领域就要归纳出所有必要的抽象角度(用例),为这些用例描述出可能的特定场景,并找到实现这些场景的事物、规则和行为。再换个说法,如果我们找到的那些事物、规则和行为实现了所有必要的用例,那么问题领域就被解决了。总之,实现用例是必须做的工作,一旦用例实现了,问题领域就解决了。这就是用例驱动方法的原理。
在实际的软件项目中,一个软件要实现的功能通过用例来捕获,接下来的所有分析、设计、实现、测试都由用例来驱动,即以实现用例为目标。在统一过程中,一个用例就是一个分析单元、设计单元、开发单元、测试单元甚至部署单元。在统一过程中用例能够驱动的不仅仅是分析设计,请看图2.2的用例驱动视图,它来自统一过程。
在统一过程中,用例捕获了系统的功能性需求。参照建模公式,我们确定它代表了软件系统要解决的问题领域。以下内容部分摘自统一过程的官方文档,用例可以驱动的内容包括:
■ 逻辑视图
系统只有一个逻辑视图,该视图以图形方式说明关键的用例实现、子系统、包和类,它们包含在构架方面具有重要意义的行为,即建模公式中的那些“人”、“事”、“物”、“规则”是如何分类组织的。
■ 进程视图
为了便于理解系统的进程组织,在“分析设计”工作流程中使用了名为进程视图的构架视图。系统只有一个进程视图,它以图形方式说明了系统中进程的详细组织结构,其中包括类和子系统到进程和线程的映射,即建模公式中的那些“人”、“事”、“物”、“规则”是如何交互的,它们的关系如何。这个视图便是我们常说的分析设计视图。
■ 部署视图
系统只有一个部署视图,它以图形方式说明了处理活动在系统中各节点的分布,包括进程和线程的物理分布,即建模公式中的那些“人”、“事”、“物”、“规则”是如何部署在物理节点(主机、网络环境)上的。
■ 实施视图
实施视图的作用是获取为实施制定的构架决策。实施视图通常包括以下内容:
➢ 列举实施模型中的所有子系统。
➢ 描述子系统如何组织为层次和分层结构的构件图。
➢ 描述子系统间的导入依赖关系的图解。
实施视图用于:
➢ 为个人、团队或分包商分配实施工作。
➢ 估算要开发、修改或删除的代码数量。
➢ 阐明大规模复用的理由。
➢ 考虑发布策略。
2.3 抽象层次
首先,抽象层次越高,具体信息越少,但是概括能力越强;反之,具体信息越丰富,结果越确定,但相应的概括能力越弱。从信息的表达能力上说,抽象层次越高表达能力越丰富,越容易理解。可能有人会对这个提出疑问,因为在人们的印象里,越是抽象的东西越难理解,相反越具体的事物越容易认识,难道不是吗?
实际上,由于人脑对信息的处理能力是有限度的,如果信息量超过了人脑的处理能力,人就会失去对这个事物的理解能力。因此,越是具体的表达信息量越大,越接近人脑的处理极限,人们的理解能力越是下降(这也是面向过程方法为什么困难的原因之一)。对面向对象方法来说,这时就需要提高抽象层次,用一个新的概念来概括一部分相关的信息,一旦人们像接受了光年概念一样消化了这个新的概念,理解起来就变得容易了。同时,这个新的概念就屏蔽了(或者说封装了)更多具体的信息。抽象层次越高,被屏蔽的信息也就越多,信息量越少,也就越容易理解和处理。这就是面向对象比面向过程具有优势的原因。
抽象有两种方法,一种是自顶向下,另一种是自底向上。自顶向下的方法适用于让人们从头开始认识一个事物。例如介绍汽车的工作原理时,从发动机、传动装置、变速器等较高层次的抽象概念来讲就比较容易明白。如果降一个层次,从发动机原理讲起,一大部分听众就会开始迷惑;再降一个层次,从热力学原理和力学原理讲起,那就更没人能搞懂汽车是怎么工作的了。自底向上的方法适用于在实践中改进和提高认识。例如在实践中发现了发动机的问题,因而改进发动机结构,甚至采用新的发动机原理,最终能够提升汽车的质量。
在软件开发过程中,主体上应当采用自顶向下的方法,用少量的概念覆盖系统需求,再逐步降低抽象层次,直到代码编写。同时应当辅以自底向上的方法,通过总结在较低抽象层次的实践经验来改进较高层次的概念以提升软件质量。现在请读者回忆一下建模公式,公式中所表达的也是一个自顶向下的方法。如果你在做设计或分析的时候总是觉得信息之间的关系太复杂,总是难以理清头绪,你需要思考一下是否不适当地采用了自底向上的分析方法。图2.3展示了通常的统一过程抽象层次和分析过程。
与抽象层次相关的另一个问题是边界,实际上抽象层次与边界的选择总是相生相伴的。
2.4 视图
但是很多时候,仅仅给出所有属性的视图并不足够,观察者会抱怨视图表达的信息不是很清晰,希望从更多角度来查看事物的信息。这就引出了视图中另一个被很多人忽视的重要概念:视角。
视角是人们观察事物的角度。不同的人或者同一个人出于不同的目的会对同一个信息从不同的角度来审视和评估。视角是针对每一个视图来说的,不同的视角展示了同样信息的不同认知角度以便于理解。例如,我们刚刚说过对汽车而言,外观是汽车属性的一部分,那么外观是不是一个视图就足够了呢?不是的,同样是查看外观,人们有时候从前面看汽车的前脸长什么样,有时候从侧面看车身流线长什么样,有时候从后面看尾箱长什么样。每一个不同的观察角度都展示了整体信息的一个部分,这个部分也满足了观察者的某一个审视要求。
回到建模工作中来,建立模型的目的是向相关的人(干系人)展示将要生产的软件产品,一个软件产品也和汽车一样,有着很多个不同的方面。只有把这些方面都描述清楚,用很多个不同的视图去展示软件这些不同的方面——静态的、动态的、结构性的、逻辑性的等——才能够说建立了一个完整的模型。为了说明这些不同的方面,UML里定义了用例图、对象图、类图、包图、活动图等不同的视图。这些视图从不同的方面描述了一个软件的结构和组成,所有这些视图的集合表达了一个软件的完整含义。所以,建模最主要的工作就是为软件绘制那些表达软件含义的视图来完整地表达软件的含义。
同时,由于软件的干系人很多,有客户、系统分析员、架构师、设计师、开发人员、测试人员、项目经理等,他们对同样信息的审视角度是不同的。即便是客户,普通业务员和经理要求的视角也不尽相同,例如针对同一个业务模块,经理更关心整体业务流程,业务员更关心表单填写。因此建模另一项重要工作就是为不同的干系人展示他们所关心的那部分视角。比方说用例图,到底是按业务部门划分呢?还是按业务流程划分呢?是按业务人员划分呢?还是按业务模块划分呢?这里仍然需要一定的思考,如果视角选择错误,就很有可能带来信息的缺失和误解。如果把一个按跨部门业务流程划分的用例图展示给一个普通的业务人员看,就不得不另外费劲向他解释那些他不熟悉的业务,而且可能由于他并不完全理解整个业务,又给你提供了错误的反馈。错误的选择视角既费力又不讨好,常常导致需求改来改去难以确定。所以抱怨客户总是提出相互矛盾需求的分析员们,请先思考一下你是否向正确的客户询问了正确的问题,并且给他看了正确视角的内容。
视图和视角是两个被忽略的关键概念,对建立一个好的模型起着很重要的作用。为特定的信息选择正确的视图,为特定的干系人展示正确的视角并不容易,需要因时因地因人制宜。请读者带着以下两个问题开始后续的阅读,在实际工作也要经常思考这两个问题,希望读者可以早日找到正确的答案。
■ 问题一:应该为哪些软件信息绘制哪些视图?
■ 问题二:应该给哪些干系人展示哪些视角?
2.5 对象分析方法
要使用UML,面向对象的思想和方法是不可回避的。使用好UML的前提条件就是掌握了面向对象的思想和方法。下面我们就对象分析方法做一些说明。
■ 一切都是对象
在面向对象的眼里,一切有名字的东西都是对象,都应当使用对象的观点来看待它、分析它。哪怕这个东西的名字叫某某业务流程,它也仍然应当看作是一个对象,而不是一个过程。这意味着,无论什么时候都应当采用接下来讲述的一些观点和方法来看待和分析事物。
■ 对象都是独立的
独立性是面向对象的一大特点,承认对象的同时就接纳了这一观点。对象与对象之间是天然独立的,只是在某个特定的场景下,它们的某一个特定的实例才相互联系在一起。
我们获取和分析对象的手段经常是通过分析某个场景,但是需要知道,对象是离散的,它不是因为该场景而存在的。场景中的对象只是对象“映射”到该场景中的一个侧面,我们称之为对象实例。换言之,通过一个场景,我们仅能得到对象的一个侧面的信息,如图2.4所示,如果以每一个场景为坐标(维度),那么对象实例就是对象在该坐标上的投影。
从每个场景看到的仅是对象映射到该场景的一个方面,或者说是一个实例,它仅仅是对象分析的开始。请记住,当采用面向对象的方法时,在需求、分析、设计过程中,你所得到的任何一个有名字的东西,不论是用例、类、包、组件等都是独立于那个场景的,不要将对象局限在那个场景中。对象的独立性带来的正是对象的可抽象能力和可扩展能力。
■ 对象都具有原子性
无论在什么时候,在同一抽象层次上,在分析过程中都应当将对象视为一个不可分割的原子,哪怕这个对象的规模很大。例如在分析一个商业过程的时候,对象的规模(粒度)大到如银行、工厂、商场的程度,不论它有多么巨大,只要我们认为它是对象,它与其他对象交互时就是一个整体,不能分割。原子性是抽象层次有意义的重要保证,一旦破坏了原子性,则表示在同一抽象层次上的对象不具备同样的粒度,这使得分析工作陷入混乱。
在分析过程中,对象总有一个边界,永远也不应该打破边界去窥探对象的内部。形象一些说,对象看上去就像是一个个的鸡蛋,蛋壳就是对象的边界。在分析对象的过程中,我们对它的所有理解都是来自蛋壳。如果因为我们好奇心太重试图了解壳以内的世界,冲动地打破了边界,嗯,的确看到了,好奇心得到了满足,不过很快就后悔了。因为鸡蛋被破坏了,一滩粘粘乎乎的蛋清弄脏了手,很难收拾。糟糕的设计就像一堆破了壳的鸡蛋,一片混乱。
我们应当将分析过程中得到的所有对于对象的认识附加在对象边界上,在实现这个对象之前不理会其内部的细节。这称之为面向接口编程。
■ 对象都是可抽象的
对象有着很多个不同的方面。一般来说,对象参与一个场景时会展现出某一个方面。总可以将对象的某一个方面抽象出来,让其作为对象的一个代表来参与场景交互。通常这种抽象会以接口来命名。
对象所具有的方面,或者说对象所参与的场景越多,对象越有抽象价值,反之则越没有抽象价值。
■ 对象都有层次性
对象是有着抽象层次的。层次越高,其描述越粗略但适应能力越广;层次越低则描述越精确但适应能力越下降。在分析过程中,应当根据问题领域的复杂程度设定多个抽象层次,在每个层次上使用适合的抽象程度的对象描述。这将有助于显著地减少分析的难度和工作量。
不论是在需求、分析还是设计过程中,都应当具备抽象层次的观点。从需求到设计的过程已经是几个不同的抽象层次,笔者要说的是,在其中的一个阶段,例如需求阶段,仍然可以再多分几个抽象层次来说明。具体分多少抽象层次应视问题领域的复杂程度而定。
■ 对象分析方法总结
只供参考,喜欢请支持正版图书