10.3.4 确定属性(Identifying attribute) 确定属性的过程包括分析和选择两个步骤。 1. 分析(Adjective and Noun phrase for Attribute Candidate) 在需求陈述中用名词词组表示属性。 例如,“汽车的颜色”或“光标的位置”。往往用形容词表示可枚举的具体属性,例如,“红色的”、“打开的”。但是,不可能在需求陈述中找到所有属性,分析员还必须藉助于领域知识和常识才能分析得出需要的属性。
2. 选择(Choose Proper Attribute) 删掉不正确的或不必要的属性。 (1) 误把对象当作属性 例如,在邮政目录中,“城市”是一个属性,而在人口普查中却应该把“城市”当作对象。
(2) 误把关联类的属性当作一般对象的属性 如果某个性质依赖于某个关联链的存在,则该性质是关联类的属性,在分析阶段不应该把它作为一般对象的属性。 (3) 把限定误当成属性 在ATM系统的例子中,“分行代码”、“账号”、“雇员号”、“站号”等都是限定词。
(4) 误把内部状态当成了属性 如果某个性质是对象的非公开的内部状态,则应该从对象模型中删掉这个属性。 (5) 过于细化 在分析阶段应该忽略那些对大多数操作都没有影响的属性。 (6) 存在不一致的属性 类应该是简单而且一致的。如果得出一些看起来与其他属性毫不相关的属性,则应该考虑把该类分解成两个不同的类。
10.3.5 识别继承关系(Recognising Inheritance) 用两种方式建立继承(即泛化)关系: 利用继承机制共享公共性质,并对系统中众多的类加以组织。 (1) 自底向上: 抽象出现有类的共同性质泛化出父类,这个过程实质上模拟了人类归纳思维过程。 例如,在ATM系统中,“远程事务”和“柜员事务”是类似的,可以泛化出父类“事务”;类似地,可以从“ATM”和“柜员终端”泛化出父类“输入站”。
(2) 自顶向下: 把现有类细化成更具体的子类,这模拟了人类的演绎思维过程。 利用多重继承可以提高共享程度,但是同时也增加了概念上以及实现时的复杂程度。使用多重继承机制时,通常应该指定一个主要父类,从它继承大部分属性和行为;次要父类只补充一些属性和行为。 图10.5增加了继承关系之后的ATM对象模型。
10.3.6 反复修改(Repetitious Modification) 1. 分解“现金兑换卡”类 把“现金兑换卡”类分解为“卡权限”和“现金兑换卡”两个类,将使每个类的功能更单一:前一个类标志储户访问账户的权限,后一个类是含有分行代码和卡号的数据载体。多张现金兑换卡可能对应着相同的访问权限。
2. “事务”由“更新”组成 通常,一个事务包含对账户的若干次更新,这里所说的更新,指的是对账户所做的一个动作(取款、存款或查询)。“更新”虽然代表一个动作,但是它有自己的属性(类型、金额等),应该独立存在,因此应该把它作为类。 3. 把“分行”与“分行计算机”合并 区分“分行”与“分行计算机”,对于分析这个系统来说,并没有多大意义,为简单起见,应该把它们合并。类似地,应该合并“总行”和“中央计算机”。
10.4 建立动态模型(Dynamic Modeling) 第一步是编写典型交互行为的脚本。 第二步,从脚本中提取出事件,确定触发每个事件的动作对象以及接受事件的目标对象。 第三步,排列事件发生的次序,确定每个对象可能有的状态及状态间的转换关系,并用状态图描绘它们。 最后,比较各个对象的状态图,检查它们之间的一致性,确保事件之间的匹配。 本节结合ATM系统的实例,进一步讲述建立动态模型的方法。
10.4.1 编写脚本(Writing Script) 在建立动态模型的过程中,脚本是指系统在某一执行期间内出现的一系列事件。 在建立动态模型的过程中,脚本是指系统在某一执行期间内出现的一系列事件。 脚本描述用户(或其他外部设备)与目标系统之间的一个或多个典型的交互过程,以便对目标系统的行为有更具体的认识。 编写脚本的目的,是保证不遗漏重要的交互步骤,它有助于确保整个交互过程的正确性的和清晰性。 ATM系统的正常情况脚本和异常情况脚本(240页)。
10.4.2 设想用户界面(User Interface Design) 大多数交互行为都可以分为应用逻辑和用户界面两部分。 采用不同界面(例如,命令行或图形用户界面),可以实现同样的程序逻辑。应用逻辑是内在的、本质的内容,用户界面是外在的表现形式。 动态模型着重表示应用系统的控制逻辑。
图10.7 ATM的界面格式
10.4.3 画事件跟踪图(Drawing Events Track Diagram) 完整、正确的脚本为建立动态模型奠定了必要的基础。但是,用自然语言书写的脚本往往不够简明,而且有时在阅读时会有二义性。为了有助于建立动态模型,通常在画状态图之前先画出事件跟踪图。为此首先需要进一步明确事件及事件与对象的关系。
1. 确定事件(Identifying events) 应该仔细分析每个脚本,以便从中提取出所有外部事件。 2. 画出事件跟踪图(Drawing Events Track Diagram) 从脚本中提取出各类事件并确定了每类事件的发送对象和接受对象之后,就可以用事件跟踪图把事件序列以及事件与对象的关系,形象、清晰地表示出来。事件跟踪图是简化的UML顺序图。 在事件跟踪图中,一条竖线代表一个对象,每个事件用一条水平的箭头线表示,箭头方向从事件的发送对象指向接受对象。时间从上向下递增。图中仅用箭头线在垂直方向上的相对位置表示事件发生的先后。
10.4.4 画状态图(Drawing States Diagram) 状态图描绘事件与对象状态的关系。当对象接受了一个事件以后,引起的状态改变称为“转换”。 用一张状态图描绘一类对象的行为,它确定了由事件序列引出的状态序列。仅考虑具有重要交互行为的那些类。 考虑事件跟踪图中指向某条竖线的那些箭头线。把这些事件作为状态图中的有向边(即箭头线),边上标以事件名。两个事件之间的间隔就是一个状态。 从事件跟踪图中的竖线射出的箭头线,是这条竖线代表的对象达到某个状态时所做的行为(往往是引起另一类对象状态转换的事件)。
考虑完正常事件之后再考虑边界情况和特殊情况,出错处理是不能省略的。 “ATM”、“总行”和“分行”的状态图。 这些状态图都是简化的,尤其对异常情况和出错情况的考虑是相当粗略的。
图10.10 总行类的状态图
图10.11 分行类的状态图
10.4.5 审查动态模型(Review Dynamic Model) 各个类的状态图通过共享事件合并起来,构成了系统的动态模型。 在完成了每个具有重要交互行为的类的状态图之后,应该检查系统级的完整性和一致性。
10.5 建立功能模型(Function Modeling) 功能模型表明了系统中数据之间的依赖关系,以及有关的数据处理功能,它由一组数据流图(DFD)组成。其中的处理功能可以用IPO图(或表)、伪码等多种方式进一步描述。 通常在建立了对象模型和动态模型之后再建立功能模型。
10.6 定义服务(Definition Service) 1. 常规行为( Actions) 在分析阶段可以认为,类中定义的每个属性都是可以访问的,也就是说,假设在每个类中都定义了读、写该类每个属性的操作。但是,通常无需在类图中显式表示这些常规操作。
2. 从事件导出的操作(Operations) 状态图中发往对象的事件也就是该对象接收到的消息,因此该对象必须有由消息选择符指定的操作,这个操作修改对象状态(即属性值)并启动相应的服务。 例如,在ATM系统中,发往ATM对象的事件“中止”,启动该对象的服务“打印账单”; 发行分行的事件“请分行验卡”启动该对象的服务“验证卡号”; 而事件“处理分行事务”启动分行对象的服务“更新账户”。 所启动的这些服务通常就是接受事件的对象在相应状态的行为。
3. 与数据流图中处理框对应的操作(Process of DFD) 数据流图中的每个处理框都与一个对象(也可能是若干个对象)上的操作相对应。 应该仔细对照状态图和数据流图,以便更正确地确定对象应该提供的服务。 例如,在ATM系统中,从状态图上看出分行对象应该提供“验证卡号”服务,而在数据流图上与之对应的处理框是“验卡”,根据实际应该完成的功能看,该对象提供的这个服务应该是“验卡”。
4. 利用继承减少冗余操作(Inheritance Operations) 应该尽量利用继承机制以减少所需定义的服务数目。只要不违背领域知识和常识,就尽量抽取出相似类的公共属性和操作,以建立这些类的新父类,并在类等级的不同层次中正确地定义各个服务。
习题 10-1 用面向对象方法分析研究本书习题2第2题中描述的储蓄系统,试建立它的对象模型、动态模型和功能模型。 10-2 用面向对象方法分析研究本书习题2第3题中描述的机票预订系统,试建立它的对象模型、动态模型和功能模型。 10-3 用面向对象方法分析研究本书习题2第4题中描述的患者监护系统,试建立它的对象模型、动态模型和功能模型。 10-4 下面是自动售货机系统的需求陈述,请建立它的对象模型、动态模型和功能模型:
自动售货机系统是一种无人售货系统。售货时,顾客把硬币投入机器的投币口中,机器检查硬币的大小、重量、厚度及边缘类型。有效的硬币是一元币、五角币、一角币、五分币、二分币和一分币。其他货币都被认为是假币。机器拒绝接收假币,并将其从退币孔退出。当机器接收了有效的硬币之后,就把硬币送入硬币储藏器中。顾客支付的货币根据硬币的面值进行累加。
自动售货机装有货物分配器。每个货物分配器中包含零个或多个价格相同的货物。顾客通过选择货物分配器来选择货物。如果货物分配器中有货物,而且顾客支付的货币值不小于该货物的价格,货物将被分配到货物传送孔送给顾客,并将适当的零钱返回到退币孔。如果分配器是空的,则和顾客支付的货币值相等的硬币将被送回到退币孔。如果顾客支付的货币值少于所选择的分配器中货物的价格,机器将等待顾客投进更多的货币。如果顾客决定不买所选择的货物,他投放进的货币将从退币孔中退出。