用例图不仅仅是展示系统与外部参与者(Actor)以及系统功能(用例)之间的基本交互。为了更清晰、更结构化地描绘复杂的系统行为,用例图引入了几种重要的关系。这些关系允许我们将用例分解、重用公共行为、展示可选流程,或者表示参与者或用例之间的泛化/特化层级。
理解和正确使用这些关系,是用例图建模的关键,它能帮助我们构建出更准确、更易于理解的需求模型。
用例图中有哪些主要关系? (是什么)
用例图主要包含以下几种关系:
- 关联 (Association): 这是最基本的,表示参与者与用例之间的交互或通信。通常用一条实线表示。虽然它不是本文的重点,但它是图中普遍存在的关系。
- 包含 (Include): 用于抽取多个用例中的公共行为,形成一个独立的用例。
- 扩展 (Extend): 用于描述在特定条件下,一个用例会扩展另一个基本用例的行为。
- 泛化 (Generalization): 用于表示参与者或用例之间的通用与特殊关系(“is-a”关系)。
本文将重点围绕“包含”、“扩展”和“泛化”这三种特殊的用例图关系进行详细阐述。
为什么要使用这些用例图关系? (为什么)
使用包含、扩展和泛化这些关系的主要目的是为了:
- 分解复杂性: 将大型、复杂的用例分解成更小、更易于管理的子用例。
- 促进行为重用: 识别并提取多个用例共享的公共行为,避免重复建模。
- 建模可选或替代流程: 清晰地表示在特定条件下才会发生的行为,保持主流程的简洁。
- 组织和结构化模型: 通过泛化关系,展示参与者或用例的层级结构,提高模型的可读性和可维护性。
关键益处: 这些关系使得用例图能够更详细、更准确地描述系统功能,同时也提高了模型的可理解性和可维护性。
每种关系如何表示及其指向? (如何 & 哪里)
正确理解每种关系的表示符号和箭头方向至关重要,这直接影响到模型的含义。
包含 (Include)
是什么:
包含关系用于指定一个基础用例(Base Use Case)的行为中,包含了另一个包含用例(Included Use Case)的行为。这通常用来提取多个用例中的公共步骤或子任务。
重要的是,包含用例的行为是基础用例流程中必需的或必须执行的一部分,尽管它被抽取出来独立表示。
如何表示:
用例图中的包含关系表示为从基础用例指向包含用例的一条带箭头的虚线,箭头上标记有 <<include>> 构造型(stereotype)。
箭头的指向 (哪里):
箭头从调用方(基础用例)指向被调用方(包含用例)。记住:基础用例 <
示例场景:
用例 “处理订单” 和 “处理退货” 都需要验证用户身份。
可以将 “验证用户身份” 抽取为一个独立的包含用例。
关系:
“处理订单” ———> “验证用户身份” (<<include>>)
“处理退货” ———> “验证用户身份” (<<include>>)
扩展 (Extend)
是什么:
扩展关系用于指定一个扩展用例(Extending Use Case)在特定条件下,可以扩展或插入到另一个基础用例(Base Use Case)的行为中。这通常用来描述可选的、替代的或异常的处理流程。
扩展用例的行为是可选的,它是否发生取决于基础用例中的扩展点(Extension Point)以及相关的条件。
如何表示:
用例图中的扩展关系表示为从扩展用例指向基础用例的一条带箭头的虚线,箭头上标记有 <<extend>> 构造型。
箭头的指向 (哪里):
箭头从扩展方(扩展用例)指向被扩展方(基础用例)。记住:扩展用例 <
与包含关系的方向相反,这是理解扩展关系时最容易混淆的地方。
示例场景:
用例 “用户登录” 在输入密码错误3次后,可能会触发 “锁定用户账户” 的行为。
可以将 “锁定用户账户” 作为扩展用例。
关系:
“锁定用户账户” ———> “用户登录” (<<extend>>)
在用例 “用户登录” 的详细描述中,会定义一个扩展点,例如“密码验证失败”,并说明当该扩展点被触发且条件满足时,执行 “锁定用户账户” 用例。另一个示例:
用例 “在线购物” 在满足一定条件(如订单金额达到阈值)时,可以应用优惠。
可以将 “应用优惠” 作为扩展用例。
关系:
“应用优惠” ———> “在线购物” (<<extend>>)
泛化 (Generalization)
是什么:
泛化关系用于表示元素(可以是参与者或用例)之间的继承结构,即一个元素是另一个更通用元素的特殊类型(“is-a”关系)。特殊元素(子类)继承通用元素(父类)的所有特性、关系和行为,并可以添加自己的特定特性或行为。
如何表示:
用例图中的泛化关系表示为从特殊元素指向通用元素的一条带空心箭头的实线。
箭头的指向 (哪里):
箭头从特殊方(子类/具体项)指向通用方(父类/抽象项)。记住:特殊项 —-|> 通用项。
泛化应用于哪里? (哪里)
参与者泛化:
当多个参与者共享一部分角色或与系统有共同的交互方式时,可以提取一个更通用的参与者。
示例场景:
系统有 “注册用户” 和 “游客” 两种参与者。”注册用户” 拥有 “游客” 的所有权限,并且还有额外的权限(如发表评论)。
关系:
“注册用户” —-|> “游客”这表示 “注册用户” 是 “游客” 的一种特殊类型。
用例泛化:
当多个用例是同一个通用过程的不同变体或细化时,可以提取一个更通用的用例。特殊用例继承通用用例的基本流程,并可能在其中添加或修改特定步骤。
示例场景:
系统支持多种支付方式:”使用信用卡支付”、”使用支付宝支付”、”使用微信支付”。它们都是 “处理支付” 的一种特殊形式。
关系:
“使用信用卡支付” —-|> “处理支付”
“使用支付宝支付” —-|> “处理支付”
“使用微信支付” —-|> “处理支付”这表示具体的支付方式是用例 “处理支付” 的特殊类型。
如何区分包含和扩展关系? (如何)
这是初学者最常遇到的困惑。核心区别在于行为的强制性和触发条件,以及箭头的方向。
- 包含 (Include):
- 表示公共、重复的子行为。
- 当基础用例执行到包含点时,包含用例的行为是必须被执行的。
- 箭头方向:基础用例 –> 包含用例。
- 用于重用功能模块。
- 扩展 (Extend):
- 表示可选的、替代的或异常的处理行为。
- 只有当满足特定条件并在基础用例的特定扩展点处,扩展用例的行为才可能被执行。
- 箭头方向:扩展用例 –> 基础用例。
- 用于表示功能的变体、可选部分或异常流程。
决策准则:
问自己:这个被抽取的行为是基础用例流程中总是会发生的吗?
如果“是”,并且是为了重用,使用<<include>>。
如果“否”,它只在特定条件下或某个点发生,并且是可选的或异常的流程,使用<<extend>>。
一个用例或参与者可以有多少关系? (多少)
一个用例或参与者可以与多个其他用例或参与者建立关系。
- 一个基础用例可以
<<include>>多个包含用例。 - 一个包含用例可以被多个基础用例
<<include>>。 - 一个基础用例可以被多个扩展用例
<<extend>>。 - 一个扩展用例可以
<<extend>>多个基础用例(尽管这种情况相对少见且可能意味着模型需要简化)。 - 一个通用参与者或用例可以被多个特殊参与者或用例泛化。
- 一个特殊参与者或用例只能泛化自一个通用参与者或用例(单继承)。
关系的“多少”没有固定上限,但过多的关系会使图变得复杂难以理解。应该力求用最简洁清晰的方式表达需求。
使用用例图关系需要注意什么? (如何)
- 明确箭头方向: 这是最容易出错的地方。记住 Include 是从 Base 到 Included,Extend 是从 Extending 到 Base,Generalization 是从 Specific 到 General。
- 描述扩展点: 对于使用了 Extend 关系的用例图,务必在基础用例的详细描述中明确定义扩展点及其触发条件。这对于理解扩展行为何时以及为何发生至关重要。
- 避免过度使用: 虽然关系有助于分解和重用,但过度细化或不恰当使用关系(例如,将非常简单的步骤抽取为包含用例)反而会增加模型的复杂性。
- 保持一致性: 在整个建模过程中,对关系的理解和使用应保持一致。
- 关系与流程: 包含和扩展关系描述的是用例之间的行为调用或插入,它们影响着用例流程的执行。
总结
用例图中的包含、扩展和泛化关系是强大的建模工具,它们使得用例图能够超越简单的交互表示,更深入地刻画系统功能的结构和行为变体。
正确地识别和应用这些关系,特别是区分包含和扩展,并清晰地描述相关的扩展点,是构建高质量用例模型的关键。它们有助于提高模型的准确性、可读性和可维护性,为后续的系统设计和开发奠定坚实基础。