模型的要素——用例、视图和构造块
-
模型的构建步骤
1)从用例场景开始,给模型输入概念、属性、术语。
2)构建静态领域模型(类图),发现领域概念和对象属性。
3)构建动态领域模型(时序图和协作图),发现对象之间的关系、行为和事件(步骤2、3交替进行)。
4)使用操作契约来检查和完善设计,完成领域模型的单元测试用例。
-用例
用例是一组相关的成功和失败的场景集合,用来描述参与者如何使用系统来实现其目标
参与者是某些具有行为的事物,可以是人(由角色表示)、组织或其他计算机系统。
场景是参与者和系统之间的一系列特定活动和交互,是使用系统的一个特定情节或用例的一条执行路径。
例如,顾客下单、网络支付都属于一个场景
用例强调了用户的目标和观点。它回答了这样的问题:“谁使用系统?他们使用的典型场景是什么?业务价值是什么?”
用例由专门的UML图表示
用例图只描述了参与者与场景,可以作为用例的概括和名称,不能替代用例文本。
用例建模主要是编写文本的活动,其中列举了各类条件和分支,而不是简单绘制用例图 -
用例的主要作用如下。
1)提供了模型的工作场景。
2)提供重要概念、对象、属性、关联的输入。
3)模型的测试和验收要基于用例。
4)形成了最初的通用语言词汇表。
5)作为领域专家和业务人员的绝佳切入点。 -
发现用例的系统过程如下。
(1)确定系统边界
(2)寻找参与者和目标
(3)定义用例 -
确定参与者和目标:
❑ 谁来启动和停止系统?
❑ 谁来完成用户管理和安全管理?
❑“时间”是参与者吗?因为系统要响应时间事件来完成某些活动。
❑ 除了人作为主要参与者外,还有其他外部的系统调用该系统的服务吗?
❑ 谁来考察系统活动或性能?
❑ 谁来考察日志? -
用例的编写
(1)用例的参与者
1)主要参与者:具有用户目标,使用系统完成该目标,如顾客、管理员。主要参与者的目的是形成主要的有效用例集。
2)协助参与者:为系统和步骤1中的用例提供服务支持。支付授权服务即是一个例子,还有为系统提供数据的用户或第三方系统。
比如,你的手机定位为打车软件提供了服务。确定协助参与者,将帮助我们明确外部接口和协议
3)幕后参与者:在用例行为中具有影响或利益,但不属于前两者的参与者,如审计部门。
(2)用例的三种常用形式
用例可以以不同的详略程度进行编写,以满足不同的使用场景。
1)摘要形式。
简洁的一段式概要,通常用于主成功场景,比如下单。
用于早期讨论阶段,以使团队快速了解主题和范围,几分钟即可完成。
目标清晰,一般不会有什么歧义。
2)非正式形式。非正式的段落格式,用几个段落覆盖不同场景。
3)详述形式。详细编写所有步骤及各种变化,同时具有补充部分,如前置和后置条件。
详述形式的用例有对应的模板,展示了更多的细节,并且更为深入。优先细化核心用例,是设计领域模型的起点。
(3)好的用例风格
1)拓展视野,发现真正的客户目标
2)以本质风格编写用例。不要过早地想象用户界面,要摒弃用户界面思维,集中于意图。
本质风格:不涉及UI细节而集中于用户真实意图的编写风格。
具体风格:表达的是用户旧的操作习惯
3)采用用户价值的观点。
模型的数据:类图
-
类图里的各个元素,包括类名、属性和方法,并展示了接口和类之间的继承与依赖关系。
类图中的大部分元素都是可选的(例如,+/-可见性、参数和分栏),建模者可根据建模的业务逻辑选择呈现、隐藏或定制化这些元素属性
-
属性是类的重要成员,是数据的载体,处于类图的第一栏
-
属性一般分为3类:
1)语言层面的基本类型,如布尔类型、日期、数字、字符、字符串、日期时间。
2)通用的值类型,非基本类型且不具有特定的业务含义(即任何项目都通用),如地址、电话号码、身份证号、自定义的枚举类型等。
3)项目中的其他业务对象有专门的业务含义,其中有唯一标识的称为实体,没有标识的则为值类型。 -
(2)属性的表示方法:
前两种类型的属性采用文本的方式进行展示,即将其放在类图的第一栏中,第三种类型采用关联线的方式进行展示。
文本表示法的完整格式如下: 可见性(公有/私有保护):类型 多重性(是否集合)=默认值{属性}
比如,+spanWeeks:int=3{readonly}定义了一个公有的、整型的、默认值为3的只读属性spanWeeks(跨越周数)。 -
关联线表示法
关联线的箭头说明了从属关系。它可以包含定义的变量名称,还可以附加多重性值,如“”或“0…1”,表示数量的对应关系,关联线标识实际上已经在为业务逻辑建模了
“1…”的关系,代码层面对应的是集合,也可以添加相应的业务逻辑,比如如果集合是排好序的,可以加上{Ordered}特性
** 方法** -
方法是类的行为,处于类图的第二栏。从某种程度上说,它代表了类的行为和责任,实际上是给“静态”的类图添加了“动态”元素
完整格式如下:可见性(公有/私有保护):名称{参数列表}:返回值{方法特性}
如: +getPrice(productID:string):float{exception IOException}定义了一个公有的、以产品ID作为传入参数、返回值为浮点类型且方法内部可能抛出IO访问异常的名为getPrice(获得价格)的操作方法
注释、约束和关键字
- UML注释标签显示为折角矩形,并使用虚线连接到要注解的元素上
注释标签可以包括多种内容:
注释和说明。任何需要说明业务的地方均可使用。
方法的代码实现。
操作的实现代码可以标记出来。注释可以采用伪代码或真 实代码的片段,一般用关键字method标注。
需要注意图的空间限制,并不一定要将代码完全粘贴在注释中,只需要呈现你认为重要的部分即可。
注释标签的用法对于构建领域模型非常重要,主要体现在以下两个方面:
相对复杂找不到合适表示方法的领域逻辑,都可以暂时放在注释标签里说明,而作为模型和通用语言的一部分。
代码实现逻辑可以内置其中变为模型的一部分,这完全符合三合一的原则
- 约束
约束是重要的领域逻辑,可以完美地体现在类图上,一般使用{…}形式来表示,如{readonly}、{size>=0}。
- 关键字
UML关键字可以标记模型元素的类别。例如前面的<
-
(1)依赖
类之间的相互依赖用连接的虚线箭头表示。当一个类依赖于另一个类时,它们之间存在以下一种或多种关系:
属性是被依赖的类。
使用了该类的类名、属性、方法、静态方法等。
参数是该类的类型。
继承该类(接口则为实现)。
总之,当一个类的内部出现其他类的名字或其属性、方法时,就与后者产生了依赖,所有这些关系都可以用依赖线表示
但有些依赖类型已经有了自己的特殊线条表示法,因此使用已经约定的方法而不是虚线箭头
-
限定关联
限定关联具有限定符,这些限定符可依据键从规模较大的相对集合中选择一个或多个对象。加了限定符后,关联对象间的多重性可能会改变,通常是由多变一
Product为限定符,之前订单与折扣之间是一对多关系,即一个订单可以享受多种折扣,但指定了商品Product后变为一对一的关系,表现了对于一个商品只能享受一种折扣的需求含义。可以看到限定关联可以表达精确的业务逻辑,是我们建模的利器
** 聚合与组合**
-
类之间的聚合(Aggregation)与组合(Composition)都表示一种整体与部分的关系,
两者的区别是,聚合的部分可以脱离整体而存在,而组合的部分必须依赖于整体而存在 -
组合关系有以下几层含义:
在某一时刻,部分实例只属于一个组合实例。
部分总是属于组合,不存在脱离整体的部分,脱离整体后的部分是无意义的。
组合要负责创建和删除其部分,既可以自己来创建/删除部分,又可以与其他对象协作来创建/删除部分。
如果组合被销毁,其部分也必须被销毁。
聚合用空心的菱形箭头表示,组合用实心的菱形箭头表示
模型的行为:交互图
- 交互图是动态视图,描述了在指定的用例场景下对象间的消息传递,确定模型的行为和职责。
- 交互图分为时序图和协作图
时序图具有更丰富的符号标记和更多含义
协作图更适合在白板上绘制
时序图通过栅栏格式描述交互,在右侧添加新创建的对象。
协作图通过网络格式描述对象交互,对象可以位于图中的任何位置
-
时序图
-
生命线框图表示交互的参与者,可以将它理解为类的实例,有两种表示法
-
生命线框图是框图之下的垂直延伸线,代表对象的生命周期,
-
新创建的对象的生命线高度与之前的对象不一样,
-
被销毁的对象的生命线用“X”来标记
-
执行条是生命线上的空心长条,表示对象的控制期。
-
在执行条的时间范围内,该实体将完成一系列操作,这些操作都是同步的,意味着一个操作有了返回值才会执行下一个操作,执行条消失意味着该实体的方法执行完毕。在用工具绘图时,执行条是自动添加的。在绘制草图时,我们一般将其忽略,不绘制执行条
-
用带实心箭头的实线并附以消息表达式的方式表示对象间的每个消息(同步消息),生命线自上而下表示时间顺序。初始消息用实心圆作为起点,初始消息图中没有发送者,但每一个时序图都对应一个用例场景,如果需要,可以从对应的用例中找到发送者
-
消息表达式的标准语法:
return=message(parameter:parameterType):returnType
如果明显或不重要,可以省略类型信息。以下格式都是合法的:
buyProduct(code)
bookTicket()
d=getProductDescription(id)
d=getProductDescription(id:ItemId)
d=getProductDescription(id:ItemId):ProductDescription
返回消息有以下两种表示法:
使用上述消息表达式的标准语法,即return var=message(parameter)。
在活动条末端使用消息返回线。
-
实例的创建和销毁
-
创建对象的表示法。注意,不同于消息,对象的创建使用的是虚线
-
被创建的对象与创建它的对象不在同一高度,时序图从上到下体现的是时间顺序,低于被创建者,意味着它是之后被创建的。
-
创建消息可以解释为“调用对象构造函数”
-
销毁对象如图4-21所示。一般在没有自动垃圾回收机制的语言中需要考虑销毁对象的操作,如果我们使用的是完全的面向对象语言,如Java、C#,则可以忽略销毁对象的操作。
-
图框
-
有条件的消息和循环的逻辑,时序图有图框表示法。
-
图框将作用于时序图的部分区域,且左上角的标签表明图框的含义。
循环的图框,顾客在有“更多商品”的前提下将循环地发送图框中的消息
- 同步和异步
- 使用刺形箭头表示异步调用,实心箭头表示同步调用
- 生命线框图的两侧加竖线表示主动对象(在独立的线程中运行的实例)
Clock对象也称为主动对象,即在其执行线程中运行或控制自己线程的实例
- 协作图
- (1)链路
链路是连接两个对象的路径,它指明了对象间可能的导航和可见性
两个实体间只保留一条链路来代表关联关系,不需要每一个消息都绘制一条链路。因此,可以把链路理解为传送多种信息的导线。
消息和编号
对象间的消息可以使用消息表达式和指明消息方向的箭头来表示。因为缺少时序图的从上到下的时间顺序,所以可以为消息添加编号以表示消息的次序。不要为初始消息编号,只为两个实体间的消息编号,这样可以简化编号的层级。
编号方案如下:
第一个消息不编号,因此msg1是未编号的。
使用合法编号方案来表示后续消息的顺序和嵌套,其中嵌套的消息要使用附加数字,在外部消息编号后,前面添加引入消息编号来表示嵌套。如“1: msg2→1.1: msg3”是一条调用路径,“2: msg4→2.1: msg5→2.2: msg6”是另外一条调用路径。
- 有条件的消息
可以在顺序编号后使用带有方括号的条件子句来表示有条件的消息
- 互斥的有条件路径
- 1a和1b为互斥的消息,test表达式为真时执行1a,为假时执行1b。我们在编号之后添加字母表示互斥的分支路径
- 循环消息
当给集合的每个成员发送循环消息时,实体要表示为集合形式,如items[i]:商品列表,消息用“*”号表示循环发送。
-
异步和同步
与时序图一样,协作图也使用刺形箭头表示异步调用,使用实心箭头表示同步调用
交互图与类图的关系
模型的变化:操作契约 -
操作契约采用前置条件和后置条件的形式,描述领域模型中对象的详细变化,作为用例的执行结果。
-
操作契约可以视为用例的一部分,因为它对用例的执行效果提供了更详细的分析。
操作契约分为以下几个部分。
操作:操作的名称和参数。
交叉引用:发生此操作的用例,一般引用编号。
前置条件:执行操作之前,对系统或领域模型对象状态的重要假设。如果这些假设比较重要,需要告诉读者。
后置条件:最重要的部分,即完成操作后领域模型的状态
契约:添加商品到购物车
操作:addItemsToCart (productID:itemsID, quantity:int)
交叉引用:用例——添加商品到购物车
前置条件:登录用户在商品详情页。
后置条件:
创建了新的商品条目。
如果是第一个商品条目,创建购物车。
购物车关联了新的商品条目。
购物车新的商品条目的“数量”设为quantity。
购物车与账号相关联。
商品条目中的商品与商品库关联。
商品条目中的“库存”设为商品库存值。 -
操作契约的作用不仅限于提供模型的状态检查和支撑用例测试,还适用于模型设计阶段,
因为它详细描述了领域模型的状态变化,而无须考虑模型的实现 -
后置条件可以分为以下三种类型:
❑ 创建或删除实例。
❑ 属性值的变化。
❑ 建立或消除关联(UML图中的链接) -
操作契约准则
准则1:促进设计而不是妥协
准则2:只为复杂用例撰写操作契约
准则3:遵循步骤编写
遵循以下步骤:
1)从用例中确定操作。
2)如果操作复杂,设计到的领域模型很多或状态变化点很多,可为其构造操作契约。
3)使用以下几种类别描述后置条件:创建和删除实例;修改属性值;形成和消除关联。
4)在交叉引用中关联相关用例。