软件工程-软件工程导论(第六版) 第十章 面向对象分析(图片+文字)

1 引言

    加快脚步,将软件工程的内容整理完。刚刚看完第十章的内容,就来整理。好比大家经常说的一句话,“刚出锅的饭菜,还是热乎的。”

2 面向对象分析

    在面向对象分析中,主要由对象模型、动态模型和功能模型组成。

     面向对象分析(OOA)的关键是识别出问题域内的类与对象,并分析它们相互间的关系,最终建立起问题域的简洁、精确、可理解的正确模型。在用面向对象观点建立起的3种模型中,对象模型是最基本、最重要、最核心的。

2.1 面向对象分析的基本过程

2.1.1 概述

        面向对象分析,就是抽取和整理用户需求并建立问题域精确模型的过程。
面向对象分析过程从分析陈述用户需求的文件开始。
    接下来,系统分析员应该深入理解用户需求,抽象出目标系统的本质属性,并用模型准确地表示出来。
    在面向对象建模的过程中,系统分析员必须认真向领域专家学习。

    在面向对象建模的过程中,还应该仔细研究以前针对相同的或类似的问题域进行面向对象分析所得到的结果(可重用)。

2.1.2 3个模型和5个层次

    在面向对象分析中,主要由对象模型、动态模型和功能模型组成。对象模型是最基本、最重要、最核心的。

正如9.3节所述,面向对象建模得到的模型包含系统的3个要素,即静态结构(对象模型)、交互次序(动态模型)和数据变换(功能模型)。解决的问题不同,这3个子模型的重要程度也不同。

    在面向对象分析中,主要由对象模型、动态模型和功能模型组成。对象模型是最基本、最重要、最核心的。
正如9.3节所述,面向对象建模得到的模型包含系统的3个要素,即静态结构(对象模型)、交互次序(动态模型)和数据变换(功能模型)。解决的问题不同,这3个子模型的重要程度也不同。


    在概念上可以认为,面向对象分析大体上按照下列顺序进行:
    寻找类与对象,识别结构,识别主题,定义属性,

    建立动态模型,建立功能模型,定义服务。

2.2 需求陈述

2.2.1 书写要点

    应该描述用户的需求而不是提出解决问题的方法。应该指出哪些是系统必要的性质,哪些是任选的性质。应该避免对设计策略施加过多的约束,也不要描述系统的内部结构,因为这样做将限制实现的灵活性。
    对系统性能及系统与外界环境交互协议进行描述。对采用的软件工程标准、模块构造准则、将来可能做的扩充以及可维护性要求等方面进行描述。

    书写需求陈述时,要尽力做到语法正确,而且应该慎重选用名词、动词、形容词和同义词。系统分析员必须把需求与实现策略区分开。应该看到,需求陈述仅仅是理解用户需求的出发点,它并不是一成不变的文档。系统分析员必须与用户及领域专家密切配合协同工作,共同提炼和整理用户需求。

2.3 建立对象模型

      这个模型描述了现实世界中的“类与对象”以及它们之间的关系,表示了目标系统的静态数据结构。静态数据结构对应用细节依赖较少,比较容易确定。因此,用面向对象方法开发绝大多数软件时,都首先建立对象模型,然后再建立另外两个子模型。

需求陈述、应用领域的专业知识以及关于客观世界的常识,是建立对象模型时的主要信息来源。

       对象模型通常有5个层次。典型的工作步骤是:首先确定对象类和关联(因为它们影响系统整体结构和解决问题的方法),对于大型复杂问题还要进一步划分出若干个主题;然后给类和关联增添属性,以进一步描述它们;接下来利用适当的继承关系进一步合并和组织类。而对类中操作的最后确定,则需等到建立了动态模型和功能模型之后,因为这两个子模型更准确地描述了对类中提供的服务的需求。

2.3.1 确定类与对象

      类与对象是在问题域中客观存在的,系统分析员的主要任务就是通过分析找出这些类与对象。首先找出所有候选的类与对象;然后从候选的类与对象中筛选掉不正确的或不必要的。1. 找出候选的类与对象

2. 筛选出正确的类与对象

2.3.2 确定关联


2. 筛选

 经初步分析得出的关联只能作为候选的关联,还需经过进一步筛选,以去掉不正确的或不必要的关联。

筛选时主要根据下述标准删除候选的关联(1)已删去的类之间的关联

       如果在分析确定类与对象的过程中已经删掉了某个候选类,则与这个类有关的关联也应该删去,或用其他类重新表达这个关联。

(2)与问题无关的或应在实现阶段考虑的关联

       应该把处在本问题域之外的关联或与实现密切相关的关联删去

(3)瞬时事件

关联应该描述问题域的静态结构,而不应该是一个瞬时事件。

(4)三元关联

 三个或三个以上对象之间的关联,大多可以分解为二元关联或用词组描述成限定的关联。

(5)派生关联

应该去掉那些可以用其他关联定义的冗余关联。

3. 进一步完善

通常从下述几个方面进行改进

(1)正名  (2)分解 (3)补充 (4)表明重数

2.3.3 划分主题

在开发大型、复杂系统的过程中,为了降低复杂程度,人们习惯于把系统再进一步划分成几个不同的主题,也就是在概念上把系统包含的内容分解成若干个范畴。

2.3.4 确定属性

属性是对象的性质,藉助于属性人们能对类与对象和结构有更深入更具体的认识。注意,在分析阶段不要用属性来表示对象间的关系,使用关联能够表示两个对象间的任何关系,而且把关系表示得更清晰、更醒目。

一般说来,确定属性的过程包括分析和选择两个步骤。

2.3.5 识别继承关系

       确定了类中应该定义的属性之后,就可以利用继承机制共享公共性质,并对系统中众多的类加以组织。正如以前曾经强调指出过的,继承关系的建立实质上是知识抽取过程,它应该反映出一定深度的领域知识,因此必须有领域专家密切配合才能完成。通常,许多归纳关系都是根据客观世界现有的分类模式建立起来的,只要可能,就应该使用现有的概念。

2.3.6 反复修改

     仅仅经过一次建模过程很难得到完全正确的对象模型。事实上,软件开发过程就是一个多次反复修改、逐步完善的过程。在建模的任何一个步骤中,如果发现了模型的缺陷,都必须返回到前期阶段进行修改。

      由于面向对象的概念和符号在整个开发过程中都是一致的,因此远比使用结构分析、设计技术更容易实现反复修改、逐步完善的过程。

实际上,有些细化工作(例如定义服务)是在建立了动态模型和功能模型之后才进行的。

2.4 建立动态模型

建立动态模型
第一步,是编写典型交互行为的脚本。虽然脚本中不可能包括每个偶然事件,但是,至少必须保证不遗漏常见的交互行为。
第二步,从脚本中提取出事件,确定触发每个事件的动作对象以及接受事件的目标对象。
第三步,排列事件发生的次序,确定每个对象可能有的状态及状态间的转换关系,并用状态图描绘它们。

最后,比较各个对象的状态图,检查它们之间的一致性,确保事件之间的匹配。

2.4.1 编写脚本

       在建立动态模型的过程中,脚本是指系统在某一执行期间内出现的一系列事件。脚本描述用户(或其他外部设备)与目标系统之间的一个或多个典型的交互过程(事件序列),以便对目标系统的行为有更具体的认识。

       脚本描写的范围并不是固定的,既可以包括系统中发生的全部事件,也可以只包括由某些特定对象触发的事件。脚本描写的范围主要由编写脚本的具体目的决定。

       因此,编写脚本的过程,实质上就是分析用户对系统交互行为的要求的过程。在编写脚本的过程中,需要与用户充分交换意见,编写后还应该经过他们审查与修改。

编写脚本时,
首先编写正常情况的脚本。
然后,考虑特殊情况,例如输入或输出的数据为最大值(或最小值)。
最后,考虑出错情况,例如,输入的值为非法值或响应失败。对大多数交互式系统来说,出错处理都是最难实现的部分。如果可能,应该允许用户“异常中止”一个操作或“取消”一个操作。

此外,还应该提供诸如“帮助”和状态查询之类的在基本交互行为之上的“通用”交互行为。

2.4.2 设想用户界面

        大多数交互行为都可以分为应用逻辑和用户界面两部分。通常,系统分析员首先集中精力考虑系统的信息流和控制流,而不是首先考虑用户界面。

       事实上,采用不同界面(例如命令行或图形用户界面),可以实现同样的程序逻辑。应用逻辑是内在的、本质的内容,用户界面是外在的表现形式。动态模型着重表示应用系统的控制逻辑。

       但是,用户界面的美观程度、方便程度、易学程度以及效率等,是用户使用系统时最先感受到的,用户对系统的“第一印象”往往从界面得来,用户界面的好坏往往对用户是否喜欢、是否接受一个系统起很重要的作用。

       在分析阶段不能完全忽略用户界面。在这个阶段用户界面的细节并不太重要,重要的是在这种界面下的信息交换方式。软件开发人员的目的是确保能够完成全部必要的信息交换,而不会丢失重要的信息。

       不经过实际使用很难评价一个用户界面的优劣,因此,软件开发人员往往快速地建立起用户界面的原型,供用户试用与评价。

2.4.3 画事件跟踪图

完整、正确的脚本为建立动态模型奠定了必要的基础。但是,用自然语言书写的脚本往往不够简明,而且有时在阅读时会有二义性。为了有助于建立动态模型,通常在画状态图之前先画出事件跟踪图。为此首先需要进一步明确事件及事件与对象的关系。

2.4.4 画状态图

       状态图描绘事件与对象状态的关系。当对象接受了一个事件以后,它的下个状态取决于当前状态及所接受的事件。由事件引起的状态改变称为“转换”。如果一个事件并不引起当前状态发生转换,则可忽略这个事件。

       通常,用一张状态图描绘一类对象的行为,它确定了由事件序列引出的状态序列。

       但是,也不是任何一个类都需要有一张状态图描绘它的行为。很多对象仅响应与过去历史无关的那些输入事件,或者把历史作为不影响控制流的参数。对于这类对象来说,状态图是不必要的。系统分析员应该集中精力仅考虑具有重要交互行为的那些类。

       从一张事件跟踪图出发画状态图时,应该集中精力仅考虑影响一类对象的事件,也就是说,仅考虑事件跟踪图中指向某条竖线的那些箭头线。把这些事件作为状态图中的有向边(即箭头线),边上标以事件名。

       两个事件之间的间隔就是一个状态。一般说来,如果同一个对象对相同事件的响应不同,则这个对象处在不同状态。应该尽量给每个状态取个有意义的名字。通常,从事件跟踪图中当前考虑的竖线射出的箭头线,是这条竖线代表的对象达到某个状态时所做的行为(往往是引起另一类对象状态转换的事件)。

       根据一张事件跟踪图画出状态图之后,再把其他脚本的事件跟踪图合并到已画出的状态图中。为此需在事件跟踪图中找出以前考虑过的脚本的分支点(例如“验证账户”就是一个分支点,因为验证的结果可能是“账户有效”,也可能是“无效账户”),然后把其他脚本中的事件序列并入已有的状态图中,作为一条可选的路径。

考虑完正常事件之后再考虑边界情况和特殊情况

       其中包括在不适当时候发生的事件(例如系统正在处理某个事务时,用户要求取消该事务)。有时用户(或外部设备)不能做出快速响应,然而某些资源又必须及时收回,于是在一定间隔后就产生了“超时”事件。对用户出错情况往往需要花费很多精力处理,并且会使原来清晰、紧凑的程序结构变得复杂、繁琐,但是,出错处理是不能省略的。

       当状态图覆盖了所有脚本,包含了影响某类对象状态的全部事件时,该类的状态图就构造出来了。利用这张状态图可能会发现一些遗漏的情况。测试完整性和出错处理能力的最好方法,是设想各种可能出现的情况,多问几个“如果……,则……”的问题。

2.4.5 动态审查模型

       各个类的状态图通过共享事件合并起来,构成了系统的动态模型。在完成了每个具有重要交互行为的类的状态图之后,应该检查系统级的完整性和一致性。

        一般说来,每个事件都应该既有发送对象又有接受对象,当然,有时发送者和接受者是同一个对象。对于没有前驱或没有后继的状态应该着重审查,如果这个状态既不是交互序列的起点也不是终点,则发现了一个错误。

       应该审查每个事件,跟踪它对系统中各个对象所产生的效果,以保证它们与每个脚本都匹配。

2.5 建立功能模型

       功能模型表明了系统中数据之间的依赖关系,以及有关的数据处理功能,它由一组数据流图组成。其中的处理功能可以用IPO图(或表)、伪码等多种方式进一步描述。

通常在建立了对象模型和动态模型之后再建立功能模型。

2.5.1 画出基本系统模型图

       基本系统模型由若干个数据源点/终点,及一个处理框组成,这个处理框代表了系统加工、变换数据的整体功能。

       基本系统模型指明了目标系统的边界。由数据源点输入的数据和输出到数据终点的数据,是系统与外部世界之间的交互事件的参数。

2.5.2 画出功能级数据流图

把基本系统模型中单一的处理框分解成若干个处理框,以描述系统加工、变换数据的基本功能,就得到功能级数据流图。右图是ATM系统的功能级数据流图。

2.5.3 描述处理框功能

      把基本系统模型中单一的处理框分解成若干个处理框,以描述系统加工、变换数据的基本功能,就得到功能级数据流图。
描述既可以是说明性的,也可以是过程性的。说明性描述规定了输入值和输出值之间的关系,以及输出值应遵循的规律。
过程性描述则通过算法说明“做什么”。一般说来,说明性描述优于过程性描述,因为这类描述中通常不会隐含具体实现方面的考虑。

2.6 定义服务

      “对象”是由描述其属性的数据,及可以对这些数据施加的操作(即服务),封装在一起构成的独立单元。因此,为建立完整的对象模型,既要确定类中应该定义的属性,又要确定类中应该定义的服务。
       需要等到建立了动态模型和功能模型之后,才能最终确定类中应有的服务,因为这两个子模型更明确地描述了每个类中应该提供哪些服务。事实上,在确定类中应有的服务时,既要考虑该类实体的常规行为,又要考虑在本系统中特殊需要的服务。
1. 常规行为
在分析阶段可以认为,类中定义的每个属性都是可以访问的,也就是说,假设在每个类中都定义了读、写该类每个属性的操作。但是,通常无须在类图中显式表示这些常规操作。
2.从事件导出的操作
       状态图中发往对象的事件也就是该对象接收到的消息,因此该对象必须有由消息选择符指定的操作,这个操作修改对象状态(即属性值)并启动相应的服务。

3. 与数据流图中处理框对应的操作

       数据流图中的每个处理框都与一个对象(也可能是若干个对象)上的操作相对应。应该仔细对照状态图和数据流图,以便更正确地确定对象应该提供的服务。

4. 利用继承减少冗余操作

       应该尽量利用继承机制以减少所需定义的服务数目。只要不违背领域知识和常识,就尽量抽取出相似类的公共属性和操作,以建立这些类的新父类,并在类等级的不同层次中正确地定义各个服务。

3 结束语

收工啦!~

猜你喜欢

转载自blog.csdn.net/chen_yongbo/article/details/80572521