6.3 动态模型的创建
对象模型刻画了系统的静态构成,显然,我们还需要表示系统各组成部分之间的动态关系,这种动态关系可用动态模型进行表示。创建动态模型的依据依旧是用例模型,其基本任务就是用一系列UML中的模型图将用例中的文字描述进行图示化的表达。
动态模型中可以包括下列具体的模型图:
(1)交互图:构造系统顺序图,识别系统消息,围绕每一条系统消息,进行交互图的构造。
(2)状态图:针对具有复杂状态、特别是在不同状态下,将呈现出不同行为的类构造状态图。
(3)活动图:可以针对一个用例,或者一个业务流程用活动图进一步进行刻画。
6.3.1 交互图
尽管初学者认为对象模型对软件的实现具有直接的指导意义,但是从软件开发的角度来看,交互图是真正体现设计思想的地方,或者说,它是一个根据用例来定义对象模型的工具。
现给出交互图的步骤:
(1)构造系统顺序图。
(2)选择部分系统消息编写操作契约。
(3)针对每一个系统消息,构造交互图。
6.3.1.1 系统顺序图
为了更好地刻画用户与系统的交互,我们针对每一个用例,将参与者与系统之间的交互进行表达,参与者给系统发送的消息称为系统消息。显然,要开发的系统必须处理每一条系统消息。因此,通过画系统顺序图能够为交互图的构造提供准备。
在系统顺序图中,一端为发起用例的参与者对象,一端为系统。将系统作为黑盒子,如果涉及与此系统交互的其他系统,也可以将它加入其中(见图6-2)。
图6-2 系统顺序图的例子
系统消息的命名一般表达为一个动宾词组。
6.3.1.2 编写系统操作契约
对于一些复杂的系统消息(对应于系统操作),可以进一步采用系统操作契约(system operation contract)定义其对对象模型中的各种对象产生的影响,这些影响将会在交互图中加以具体刻画和体现。
操作契约包含以下内容:
(1)操作名称:给出操作名称及参数。
(2)交叉参考:给出该操作属于哪一个用例。
(3)前提条件:在执行该操作前,系统或者对象模型中涉及的对象应该满足的条件。
(4)后置条件:该部分最为重要。用来记录执行完该操作后,系统或者对象模型中的对象将会发生的变化。
后置条件并非用以说明执行该操作中要完成的任务,而是记录了该操作执行完毕后,系统中发生的各种变化。这种变化可以分为三种类型:
(1)引起了对象的创建或者删除。
(2)引起了某些对象内部属性值的改变。
(3)在某些对象之间形成了关联关系或者原有的关联关系消除了。
以申请使用教室用例为例,该用例中的一个系统操作为提交申请(submit Application),契约描述如下:
契约CO1:submit App lication
●操作:submit Application(applier:Applier,apply Date:Date,apply Time:Time, apply Purpose:String,number Of Users:integer,classroom Requirement:string)
●交叉引用:申请使用教室
●前置条件:正在进行的教室使用申请
●后置条件:
创建了Classroom Application的实例ca
基于applier.applier ID的匹配,将ca与Teacher或Student关联
ca.apply Date赋值为apply Date
ca.apply Time赋值为apply Time
ca.apply Purpose赋值为apply Purpose
ca.number Of Users赋值为number Of Users
ca.Classroom Requirement赋值为classroom Requirement
定义操作契约带来的好处就是为构造交互图给予明确指导。
需要再次指出的是,并非每一个系统消息都需要定义系统操作契约,只需要为那些可能引起比较复杂的变化的消息定义操作契约。
6.3.1.3 交互图构造
交互图可以分成顺序图和协同图,顺序图与协同图在语义上是相等的,画一个图就可以生成另一个图,因此以顺序图为例来讲解交互图的构造。
1)软件工程的原则与责任分配
在画顺序图时,从一个对象(对象A)发送一个消息给另一个对象(对象B),其意义在于对象A请求对象B的服务,换言之,将某一个责任分配给了对象B。那么,为什么将责任分配给对象B而不是其他对象呢?这后面的依据是软件工程中的一个核心思想:高内聚和低耦合。高内聚和低耦合的提出能够提高软件的可理解性和可维护性。
类的高内聚有两层意思:
(1)一个类的功能比较单一,这可以称为语义内聚。
(2)类内部的各个方法调用可以比较多,这称为关系内聚。
低耦合的意思是各个类之间的关系应该尽可能少。
显然高内聚、低耦合使得某一个类的修改对于别的类的影响会比较小,同时,系统的重用性也会比较高,因为重用系统的某一个部分不会让其他的类过多地牵涉其中。
在分配每一个责任时,都必须考虑这个原则。那么如何进行分配才使得系统能够高内聚、低耦合呢?从高内聚的角度看,每一个类的职责应该比较明确,UI层的类就负责UI的事情,而不能去涉及业务逻辑的处理;控制类就是负责联系和协调,也不去涉及业务逻辑的处理;每一个实体类在处理业务逻辑也是有明确的分工。从低耦合的角度看,如果某些类的对象之间确实需要进行交互或者本身就有一定的耦合性(如包含被包含的关系),那么在后续进行责任分配的时候,尽量不要把别的对象牵涉进来,这样就能够降低耦合性。
高内聚、低耦合的原则在使用时也并不是金科玉律,在某些时候,与一些类库、提供公共服务的类如数据存储服务类的耦合是可以接受的,因为它们本身是比较稳定的。
上述谈及的高内聚、低耦合原则似乎是针对软件设计的。前面又多次声明分析阶段还是侧重于理解需求,因此分析阶段获取的类并非软件类,那么为什么也强调需要运用这个原则呢?因为此处分析出来的对象模型、动态模型是后续设计的直接指导,在设计阶段实际上是在对象模型、动态模型上进一步细化,并按照软件系统的一些特点做一些调整,因此,在分析阶段就需要贯彻此思想。
2)顺序图的构建
围绕每一个由参与者发出的系统消息都需要单独构造一张顺序图。当然,如果围绕这一系统消息的顺序图比较简单,可以和相关的系统消息的顺序图合并。在画顺序图时,可以遵从以下的规则:
(1)左边第一列为发出系统消息的参与者对象。
(2)第二列一般为一个边界类对象,代表与用户交互的界面。
(3)第三列一般为控制类对象,由边界类对象创建,该对象负责协调接下来的流程。通常有两种控制类对象,一种为针对一个用例的控制类对象,它负责这个用例的所有流程协调;一种为针对一个子系统或者整个系统的控制类对象,这种情况下子系统或者整个系统中用例较少,因而不再单独为每个用例去创建一个控制类对象。用例控制类对象的生命周期一般与用例一样,即用例流程完成后,用例控制类对象就结束生命期。
(4)第四列可能是控制类对象创建的一些其他边界类对象,或者是实体类对象。
(5)其他不是发出系统消息的参与者一般画在最右边。
图6-3为一个顺序图的例子,针对的是系统消息submit Application()。
图6-3 顺序图的例子(申请者提交申请的顺序图)
6.3.2 状态图
状态图是对具有复杂状态的类的进一步刻画,并非每一个类都需要构造状态图,因此需要注意状态图与顺序图的差别。状态图针对的是单个类,刻画了类的对象在整个生命期内可能历经的状态变化;顺序图针对的是多个类的对象,它们协作完成整个用例流程。
教室的状态图的例子如图6-4所示。
图6-4 状态图的例子