零、前言
谈到面向对象技术的的分析和设计,自然就离不开 UML。UML 已成为业界标准,很多人知道这个概念也有再用,但笔者在工作中发现许多人其实并不擅长使用 UML 甚至没有真正理解 UML 的意义,包括笔者自己。因此笔者希望结合自己的经验和实践,写一篇 UML 的文章,帮助做面向对象的程序员朋友能更好的利用他,从而完成自己的工作。
一、什么是 UML
:::info UML是一种语言,用来描述软件生产过程中要产生的文档,统一过程则是指导如何产生这些文档以及这些文档要讲述什么的方法。
RUP和UML并不是天生一体的,它们只是软件方法和建模语言的一个完美结合
所有初学UML的朋友都会以为学习UML必须同时学习统一过程,而由于统一过程本身的庞大和复杂,成为了学习UML的障碍。更多的UML学习者不知道UML该怎么用,在什么地方用,在什么时候用,用UML的目标是什么。相信初学者最大的疑惑就是这些吧? :::
【概念】 :::info 建模(Modeling),是指通过对客观事物建立一种抽象的方法用以表征事物并获得对事物本身的理解,同时把这种理解概念化,将这些逻辑概念组织起来,构成一种对所观察的对象的内部结构和工作原理的便于理解的表达。
简单讲:建模包含两个问题,一个是怎么建,另一个是模是什么。
你试图为现实世界建模的时候,首先要决定的是抽象角度,即建立这个模型的目的是什么。具体来说,做需求的时候,首要目标不是要弄清楚业务是如何一步一步完成的,而是要弄清楚有多少业务的参与者?每个参与者的目标是什么?参与者的目标就是你的抽象角度。
第二个问题“模是什么”,则依赖于确定了抽象角度下的场景模拟。我们必须要搞清楚谁发出了什么动作,作用于什么事物,产生了怎样的后果。显然这种描述方式是过程化的。
现在回到什么是模的问题上来,一个由抽象角度确定了的目标需要由静态的事物加上特定条件下产生的一个特定的场景来完成,即静态的事物(物)+特定的条件(规则)+特定的动作(参与者的驱动)=特定的场景(事件)。
模是什么,你心中是否已经隐隐约约有了答案?是的,模就是“人”、“事”、“物”、“规则”。
:::
【建模公式】
由于设计无法自然推导出对象结构,使得对象结构到底代表了什么样的含义变得模糊不清;同时,设计如何指导编程,也成为了困扰在人们心中的一大疑问。
于是OOA(面向对象分析)方法开始走上了舞台,其中最为重要的方法便是UML的前身,即:由Booch创造的Booch方法,由Jacobson创造的OOSE、Martin/Odell方法,Rumbaugh创造的OMT、Shlaer/Mellor方法。这些方法虽然各不相同,但它们的共同的理念却是非常相似的。于是三位面向对象大师决定将他们各自的方法统一起来,在1995年10月推出了第一个版本,称为“统一方法”(Unified Method 0.8)。随后,又以“统一建模语言”(Unified Modeling Language)UML 1.0的正式名称提交到OMG(对象管理组织),在1997年1月正式成为一种标准建模语言。
如上所述UML是一种建模用的语言,而所有的语言都是由基本词汇和语法两个部分构成的,UML也不例外。UML定义了一些建立模型所需要的、表达某种特定含义的基本元素,这些元素称为元模型,相当于语言中的基本词汇,例如用例、类等。
UML还定义了这些元模型互相之间关系的规则,以及如何用这些元素和规则绘制图形以建立模型来映射现实世界;这些规则和图形称为表示法或视图(View),相当于语言中的语法。如同我们学习任何一种语言一样,学习UML无非是掌握基本词汇的含义,再掌握语法,通过语法将词汇组合起来形成一篇有意义的文章。
谈到语言,我们无法回避的一个问题是沟通。如果不能用于沟通,那语言就没有意义。而要最大程度地沟通,那么最好的办法就是创造一种大家都认同的统一语言。
统一的意义似乎不用多说,秦始皇历史上的一大功绩便是统一语言和度量衡。统一的目标就是形成标准。对于现代社会来说,标准被广泛应用的重要程度不亚于互联网的发明,各行各业无不纷纷制定各式各样的标准。标准使得不同地域、不同文化、不同社会、不同组织的信息能够以所有人都明白的表述和所有人都遵从的格式在人群中无障碍地流通。
而在软件界,任何一种组件化开发模式背后都有一个标准在规范和指导,可以说没有标准就没有工业现代化,没有标准就没有编程组件化,这就是标准的意义;回到UML来说,就是统一的意义。
二、为什么需要 UML
:::info 软件项目真正的灵魂是软件过程,软件过程的需要才是这些工具和语言诞生的原因。 :::
软件开发工作被横向分工化的一个显著的例子便是软件外包,承包商采集需求,设计团队进行设计,然后把编码工作外包给另一个公司来完成。
软件开发工作中这种将角色细分,将职责明确的做法,在提高专业化和资源效率的同时也带来了严重的沟通问题。
文档从一个角色传向另一个角色,从一个组织传向另一个组织的过程中如何保证信息被准确地传达和准确地理解呢?一种好办法就是大家都使用统一的或者说标准化的语言。
UML 统一建模语言的意义也在于试图用统一的语言来覆盖整个软件过程,让不同的团队用同样的语言顺畅的沟通。
【意义之二】
统一语言的另一个意义是要让人和机器都能读懂
【意义之三】
统一的任务完成了,接下来的任务就是可读性。如果语言可读性很差,人们在理解起来同样会有困难。一门好的语言要能够让人们快速理解并留下深刻印象。
我们知道,相对文字和图形,人脑对图形的接受能力显然更强。因此,UML采用了“可视化”的图形方式来定义语言。
把“隐晦”的变成“可视”的,也就是把文字变成图形,这才是UML可视化的真正含义。
【举例】对于没看过汽车的人看到一段描述汽车的话,看懂会比较困难,但用图形来表达就很容易,并且让作者和读者之间不会产生歧义。
【汽车的 UML】
【现实世界到业务模型】
建立模型的过程是一个抽象的过程,所以要建立模型,首先要知道如何抽象现实世界。
现实世界运行四要素:人、事、物和规则。
人是一切的中心,人要做事,做事就会使用一些物并产生另一些物,同时做事需要遵循一定的规则。
人驱动系统,事体现过程,物记录结果,规则是控制。
建立模型的关键就是弄明白有什么人,什么人做什么事,什么事产生什么物,中间有什么规则,再把人、事、物之间的关系定义出来,一个模型也就基本成型了。
【人、事、物、规则就是这样被模型化的。】
第一,UML采用称之为参与者(actor)的元模型作为信息来源提供者,参与者代表了现实世界的“人”。参与者是模型信息来源的提供者,也是第一驱动者。
第二,UML采用称之为用例(use case)的一种元模型来表示驱动者的业务目标,也就是参与者想要做什么并且获得什么。这个业务目标就是现实世界中的“事”。而这件事是怎么做的,依据什么规则,则通过称之为业务场景(business scenario)和用例场景(use case scenario)的UML视图来描绘的,这些场景便是现实世界中的“规则”。
最后,UML通过称之为业务对象模型(business object model)的视图来说明在达成这些业务目标的过程中涉及到的事物,用逻辑概念来表示它们,并定义它们之间的关系。业务对象模型则代表了现实世界中的“物”。
UML通过上面的元模型和视图捕获现实世界的人、事、物和规则,于是现实信息转化成了业务模型,这也是面向对象方法中的第一步。业务模型真实映射了参与者在现实世界的行为,如图展示了这种映射关系
UML通过称之为概念化的过程(Conceptual)来建立适合计算机理解和实现的模型,这个模型称为分析模型(Analysis Model)。分析模型介于原始需求和计算机实现之间,是一种过渡模型。
分析模型向上映射了原始需求,计算机的可执行代码可以通过分析模型追溯到原始需求;
同时,分析模型向下为计算机实现规定了一种高层次的抽象,这种抽象是一种指导,也是一种约束,计算机实现过程非常容易遵循这种指导和约束来完成可执行代码的设计工作。
绘制分析模型最主要的元模型有:
■ 边界类(boundary)[插图]。
从广义上说,任何一件事物都分为里面和外面,外面的事物与里面的事物之间的任何交互都需要有一个边界。比如参与者与系统的交互,系统与系统之间的交互,模块与模块之间的交互等。
■ 实体类(entity)[插图]。原始需求中领域模型中的业务实体映射了现实世界中参与者完成业务目标时所涉及的事物,UML采用实体类来重新表达业务实体。
这些实体类可以看作是业务实体的实例化结果。
■ 控制类(control)[插图]。边界和实体都是静态的,本身并不会动作。UML采用控制类来表述原始需求中的动态信息,即业务或用例场景中的步骤和活动。
UML的观点看来,边界类和实体类之间,边界类和边界类之间,实体类和实体类之间不能够直接相互访问,它们需要通过控制类来代理访问要求。
一个道理:只要有人、事、物和规则(定语),就能构成一个有意义的结果,无非是是否合理而已。
对象视角,概念可被计算机理解,抽象化了的对象。
现实世界千差万别的业务,都用“边界”、“控制”、“实体”这几个固定的元素来描述
同时,这些概念还可以用“包”、“组件”等这些与现实世界毫不相关的纯计算机逻辑术语包装。
从业务模型到概念模型这一过程,正是我们需要的一种从对象世界来描述现实世界的方法。
【概念模型实例化】
概念模型到设计模型
在设计模型中,概念模型中的边界类可以被转化为操作界面或者系统接口;控制类可以被转化为计算程序或控制程序,例如工作流、算法体等;实体类可以转化为数据库表、XML文档或者其他带有持久化特征的类。
转化过程可以遵循的规则:
- 软件架构和框架。软件架构和框架规定了实现类必须实现的接口、必须继承的超类、必须遵守的编程规则等。例如当采用J2EE架构时,Home和Remote接口就是必需的。
- 编程语言。各类编程语言有不同的特点,例如在实现一个界面或者一个可持久化类时,采用C++还是Java作为开发语言会有不同的设计要求。
- 规范或中间件。如果决定采用某个规范或采用某个中间件时,实现类还要遵循规范或中间件规定的那些必需特性。
同样的概念模型会因为选择不同而得到不同的设计模型。
- 技术选型:编程语言,软件规范,中间件
“边界”、“控制”、“实体”这些对象化的概念,虽然是计算机可以理解的,但它并不是真正的对象实例,即它们并不是可执行代码,概念模型只是纸上谈兵。真正的对象世界行为是由Java类、C++类、EJB、COM+等这些可执行代码构成的。
换句话说,设计模型是概念模型在特定环境和条件下的“实例”化,实例化后的对象行为“执行”了概念模型描述的那些信息,因此设计模型得以通过概念模型追溯到原始需求来验证对象世界是否正确反映了现实世界。
【对象世界 —> 现实世界】
UML作为标准的面向对象建模语言,它需要在某个建模方法的指导下进行建模工作。正如我们学会了一门语言,还需要知道文体一样。
统一过程是这其中最为著名的建模方法,下一节就来初步了解一下统一过程
:::info 建模方法 = 统一语言 + 统一过程 :::
统一过程,即RUP
严格说起来UML并不是一个方法,而只是一种语言。UML定义了基本元素,定义了语法,但是如果要做一个软件项目,还需要有方法的指导。
正如写文章有文法,有五言律,有七言律一样,UML也需要有方法的指导来完成一个软件项目。
【视图】
为了说明这些不同的方面,UML里定义了用例图、对象图、类图、包图、活动图等不同的视图。这些视图从不同的方面描述了一个软件的结构和组成,所有这些视图的集合表达了一个软件的完整含义。所以,建模最主要的工作就是为软件绘制那些表达软件含义的视图来完整地表达软件的含义。
:::info 因此建模另一项重要工作就是为不同的干系人展示他们所关心的那部分视角。
视图和视角是两个被忽略的关键概念,对建立一个好的模型起着很重要的作用。为特定的信息选择正确的视图,为特定的干系人展示正确的视角并不容易,需要因时因地因人制宜。 :::
在实际工作也要经常思考这两个问题,希望读者可以早日找到正确的答案。
■ 问题一:应该为哪些软件信息绘制哪些视图?■ 问题二:应该给哪些干系人展示哪些视角?
3. UML 核心元素
3.1 版型
在UML里有一个概念叫版型(stereotype),有些书里也称为类型、构造型。
在接下来的学习中,读者将会看到同一元素的大量版型定义。这就是我们要先学习版型的原因。
UML官方文档对参与者的定义为:actor是在系统之外与系统交互的某人或某事物。
3.2 参与者
小节大纲:
- 基本概念
- 定义
- 参与者位于边界之外
- 参与者可以非人
- 发现参与者
- 参与者的一个重要来源是涉众
- 业务主角
- 定义:业务主角是参与者的一个版型
- 业务工人
- 如何区分是参与者还是业务工人
- 参与者与涉众的关系
- 参与者是涉众代表。参与者对系统的要求直接影响系统的建设,他们的要求就是系统需求的来源。
- 参与者与用户的关系
- 用户(user)是指系统的使用者,通俗一点说就是系统的操作员。用户是参与者的代表,或者说是参与者的实例或代理。并非所有的参与者都是用户,但是一个用户可以代理多个参与者。
- 参与者与角色的关系
- 参与者与角色的关系角色(role)是参与者的职责。角色是一个抽象的概念,从众多参与者的职责中抽象出相同的那一部分,将其命名而形成一个角色。
- 由于一个用户可以代理多个参与者,因此一个用户可以拥有多个职责,也就是可以被指定多个角色。
- 参与者的核心地位
- 代表其利益成为涉众,代表其职责成为角色。
- 核心地位还体现在系统是以参与者的观点来决定的。
- 检查点
- 如何保证发现的参与者是正确的?
【参与者】
UML官方文档对参与者的定义为:actor是在系统之外与系统交互的某人或某事物。
参与者(人/系统)—>边界—>目标系统
参与者:
- 谁对系统有着明确的目标和要求并且主动发出动作?
- 系统是为谁服务的?
:::info
参与者可以非人:计算机系统,计时器,传感器,JMS 消息—>即启动者。
找不到启动者说明不是一个功能性需求。
:::
情景:你去 12306 APP 买票,你就是参与者,你通过电话订票,电话的客服是真正的参与者。
:::info 业务主角(business actor)是参与者的一个版型,特别用于定义业务的参与者,在需求阶段使用。 :::
处于系统边界内的人,不再是参与者,虽然的确参与了业务的执行过程,它应该被定义为业务工人(business worker)。
参与者叫主角,更符合语义。
【如何区分参与者和业务工人】
- 直接的方法:在边界之内还是边界之外。
- 边界的界定(问题答案是否定的,就是业务工人。):
- 他是主动向系统发出动作么
- 他有完整的业务目标吗
- 系统是为他服务的吗
参与者与涉众的关系涉众(stakeholder),也称为干系人。涉众是与要建设的这个系统有利益相关的一切人和事,涉众的利益要求会影响系统的建设。
【检查点】
但是如何保证发现的参与者是正确的呢?统一过程的官方文档里给出了一个检查点列表,回答这个检查点列表中的问题非常有助于检查发现的参与者是否正确,读者可以参考之。
- 是否找到全量参与者(所有角色)
- 每个参与者是否都有用例
- 参与者角色是否有重叠,考虑参与者合并
- 参与者是否担任用例的同一角色,用泛化关系为共享行为建立模型。
- 参与者是否以不同的方式使用系统或者用例处于不同的目的,应该有多个参与者。
- 参与者是否有直观名称和描述性名称。
3.3 用例
【小节大纲】
- 基本概念
- 定义:一组用例实例,每个实例都是系统所执行的一系列操作,这些操作生成特定主角可以观测的值
- 用例的构成:参与者、前置条件、场景、后置条件
- 用例的特征
- 相对独立。
- 执行结果可观测和有意义。
- 参与者发起。
- 动宾短语形式出现。(不能是喝这种动词。)
- 一个用例是各个维度的单元。
- 用例的粒度
- 项目阶段不同,使用不同的粒度。
- 用例的获得
- 区分业务规则、过程步骤、超出边界范围、还是具有有效完整目标的用例
- 用例和功能的误区
- 目标和步骤的误区
- 用例粒度的误区
- 原因:分不清目标和步骤。
- 举例:系统管理员的修改订单操作不能作为单独用例,而应该是买家购买商品的子过程。
- 用例驱动软件架构的建立,错误的建模会将强依赖关系的逻辑分成在架构上要求独立的模块,形成糟糕的设计。
- 系统管理员作为业务工人参与购买商品业务,如果后续这个业务工人的角色不再由系统管理员承担,带来的程序的变动总是很大的。
- 那么应当怎么处理系统管理员修改定单这个业务过程呢?办法是紧守边界,认识到现在的抽象层次高于这个业务需求,它不能作为一个单独的用例在这个抽象层次上出现。
- 业务用例
- 定义
- 业务用例实现
- 定义
- 类比编程上的接口和实现类的关系
- 概念用例
- 被忽略但重要
- 系统用例
- 未定义版型,但是天天挂在嘴边的用例。
- 含义
- 业务用例是客户业务视角,系统用例是系统视角。
- 用例实现
- 用例实现时连接起用例模型和系统实现之间的桥梁。
【用例】
用例在UML建模中是最最重要的一个元素。之所以说它重要,是因为UML是面向对象的,除用例之外,所有其他元素都是“封装”的、“独立”的。
面向对象方法中讲到的内容,这些元素在没有“外力”作用时是“鸡犬之声相闻,老死不相往来”的。而用例正是施加这一“外力”的元素,正是用例使得其他那些“孤独”的UML元素能够共同组成一篇有意义的文字。因而没有准确的用例定义一切都无从谈起。
【基本概念】
综上所述,一个完整的用例定义由参与者、前置条件、场景、后置条件构成。图3.8展示了用例的结构。[插图]图3.8 用例的构成
【用例的特征】
- 用例是相对独立的。
- 用例的执行结果对参与者来说是可观测的和有意义的。
- 事必须由一个参与者发起。不存在没有参与者的用例,用例不应该自动启动,也不应该主动启动另一个用例。
- 用例必然是以动宾短语形式出现的。比如喝水是用例,但喝不是用例。
- 一个用例就是一个需求单元、分析单元、设计单元、开发单元、测试单元,甚至部署单元。
【用例的粒度】
大力度包含小粒度。
在项目过程中根据阶段不同,使用不同的粒度。在业务建模阶段,用例的粒度以每个用例能够说明一件完整的事情为宜,即一个用例可以描述一项完整的业务流程。这将有助于明确需求范围。例如取钱、报装电话、借书等表达完整业务的用例,而不要细到验证密码、填写申请单、查找书目等业务中的一个步骤。
在用例分析阶段,即概念建模阶段,用例的粒度以每个用例能描述一个完整的事件流为宜。可理解为一个用例描述一项完整业务中的一个步骤
现实情况中,一个大型系统和一个很小的系统用例粒度选择会有较大差异。这种差异是为了适应不同的需求范围。
现实情况中,一个大型系统和一个很小的系统用例粒度选择会有较大差异。这种差异是为了适应不同的需求范围。
【用例的获得】
发现用例的前提条件是发现参与者;而确定参与者的同时就确定了系统边界。在开始捕获用例之前,我们需要做好如图3.14所示的准备工作。
发现用例之前需要明确的几个点:
- 主角是位于系统边界外的。
- 主角对系统有着明确的期望和明确的回报要求。
- 主角的期望和回报要求在系统边界之内。
对主角(业务代表)访谈:
- 您对系统由什么期望?
- 您打算在这个系统里做些什么事情?
- 您做这件事的目的是什么?
- 您昨晚这件事希望有一个什么样的结果?
用例获取原则:
- 一个明确的有效的目标才是一个用例的来源。
- 一个真实的目标应当完备地表达主角的期望。
- 一个有效的目标应当在系统边界内,由主角发动,并具有明确的后果。
重新访谈,策略调整:
- 调整系统边界和主角
- 扩大或缩小系统边界
- 变更主角
- 重新开始。
举例:从访谈中选取关键的操作,识别哪些事有效用例:
:::info 客户代表(主角)说:我希望这台ATM能支持跨行业务,我插入卡片输入密码后,可以让我选择是取钱还是存钱;为了方便,可以设置一些默认的存取金额按钮;我可以修改密码,也可以挂失;还有我希望可以交纳电话费、水费、电费等费用;为了安全起见,ATM上应当有警示小心骗子的提示条,还有摄像头;如果输入三次密码错误,卡片应当被自动吞没。 :::
用例候选:
:::info 支持跨行业务?■ 插入卡片?■ 输入密码?■ 选择服务?■ 取钱?■ 存钱?■ 挂失卡片?■ 交纳费用?■ 警示骗子?■ 三次错误吞没卡片? :::
参考答案: :::info
- 支持跨行业务?×错,这是一个业务规则,限定业务的范围。
- 插入卡片?×错,这是一个过程步骤,不是完整目标。
- 输入密码?×错,这是一个过程步骤,不是完整目标。
- 选择服务?×错,这是一个过程步骤,不是完整目标。
- 取钱?√ 对,这是一个有效的完整目标。
- 存钱?√ 对,这是一个有效的完整目标。
- 挂失卡片?√ 对,这是一个有效的完整目标。
- 交纳费用?√ 对,这是一个有效的完整目标。
- 警示骗子?×错,已经超出了边界范围。
- 三次错误吞没卡片?×错,这是一个业务规则,限定业务的条件。 :::
【用例和功能的区别】
- 功能脱离使用者愿望而存在;用例描述使用者愿望,描述使用者对系统的使用要求;
- 功能是孤立的,给输入就有固定输出;用例是系统性的,描述情境方式和结果。
- 用例是众多功能的组合,目的是完成特定目标。先有场景,分解出功能。
举例:
- 功能:电视开关,调频道,调声音,能显示
- 人要观看电视节目:可以几个功能都走。
【用例粒度的误区】
【步骤不能误当做用例】
用例一定要是满足需求的完整目标。
举例说明,假设有一个网上购物系统。在获取需求时,我们决定采用整个系统的边界作为起点,那么参与者就应当是系统之外的,例如买家、卖家、系统管理员等。相应的,这些参与者的完整目标就构成用例,用例的粒度就是系统的最高层次,它们展示业务构成。例如买家购买商品,卖家发布商品,系统管理员维护网站等。
网上购物系统——符合边界
首先,作为修改定单的参与者,虽然名字仍然叫做系统管理员,但实际上就修改定单这件事情来说,系统管理员只是购买商品这个用例场景中的一个业务工人,也就是说系统管理员修改定单的目的是服务于购买商品用例的,并不是一个完整目标。这种情况下,当我们用业务模型中的用例来描述它们如何构成业务场景的时候,修改定单是无法融入到这个业务场景中的。例如这样来描述这个业务场景,说商家发布商品,买家购买商家发布的商品,系统管理员修改订单,就会显得修改定单在这个业务场景中格格不入。因为修改定单根本就和发布商品和购买商品不在一个层面上,它只是购买商品过程中的一个子过程而已。
其次,用例将驱动软件架构的建立,如果这样建模,把本来属于购买商品用例中的一个步骤提升成一个用例与购买商品用例并列,那么很自然地软件架构中系统管理员就会拥有修改定单的一个专门模块,而这个模块则必须依赖于购买商品模块,从架构上讲,两个原本关系紧密,有强依赖关系的逻辑被分成了两个在架构上要求独立的模块,总是一种很糟糕的设计。
最后,从实现上讲,系统管理员本来只是作为一个业务工人参与了购买商品业务,在程序实现上他只是通过扮演购买商品模块中的一个角色来完成修改定单的工作,一旦业务变更,设定了新的专门的交易管理员来修改定单,程序需要做的只是把修改定单的角色从系统管理员身上抹去,重新赋予交易管理员即可。如果修改定单成为系统管理员的用例,这个需求就很可能被集成到系统管理模块里去,成为系统管理员角色的一个部分。当业务变更交易管理员出现时,要么接受本来是业务人员身份的交易管理员可以操作系统管理模块,要么把修改定单这个模块从系统管理模块中迁移出来。不管哪个解决方案,总是很令人沮丧的。
那么应当怎么处理系统管理员修改定单这个业务过程呢?办法是紧守边界,认识到现在的抽象层次高于这个业务需求,它不能作为一个单独的用例在这个抽象层次上出现。
【业务用例】
业务用例[插图]业务用例(business use case)是用例版型中的一种,专门用于需求阶段的业务建模。在为业务领域建立模型时应当使用这种版型。
格来说业务建模与计算机系统建模无关,它只是业务领域的一个模型,通过业务模型可以得到业务范围,帮助需求人员理解客户业务,并在业务层面上和客户达成共识。
业务模型是系统模型的最重要输入。
之所以不能够把计算机引入进来,是因为业务范围不等于系统范围,不是所有的业务都能够用计算机来实现的。
【业务用例实现】
业务用例实现[插图]业务用例实现(business use caserealization),也称为业务用例实例,是用例版型中的一种,专门用于需求阶段的业务建模。在为业务领域建立模型时采用这种版型。
从字面上理解,业务用例实现就是业务用例的一种实现方式。一个业务用例可以有多种实现方式,它们的关系可以类比编程上的接口和实现类的关系,同一个接口可以有多个实现类。
【业务用例实现】
因此在建立业务模型的时候,我们就可以用营业厅交费、预存话费和银行代交费的业务用例实现来“实现”交纳电话费业务用例
在后续的建模过程中,我们根据业务用例实现将得出关键业务对象,再从业务对象转化到设计对象,从而生成代码。
【概念用例】
概念用例在实际的应用中很少被人使用,UML也没有为它预定版型,当然我们可以自己添加,左边的示例图中的<
概念建模很少被采用,是因为被忽视,但它很重要。
概念模型用来获取业务模型中的关键概念,分析出业务模型中的核心业务结构以得到一个易于理解的业务框架。实际上业务架构(参看5.7.1.1节业务架构)就是在这个阶段产生的。
同时,概念用例还是从业务用例到系统用例过渡时非常重要的指导。
从图3.21读者可以初步体会一下通过概念模型的建立来揭示核心业务的做法,这些核心业务可以为建立业务架构提供很好的指导,同时根据概念用例,再来发现和决定系统用例就不再显得生硬了。比如转账这项核心业务,可能由于某些原因暂不支持,只支持现金存入的方式,那么在系统用例中就可以去掉这个需求。如果没有概念用例,就很容易缺少为什么从预存话费业务用例到系统用例时某些信息丢失了的原因。
【系统用例】
系统用例没有定义版型。实际上它就是我们天天挂在嘴边的用例,因此接下来本书中把系统二字去掉,直接称之为用例。如果不是特别强调,读者可以把用例等同于系统用例。
系统用例是用来定义系统范围、获取功能性需求的。
系统用例的含义就是,系统用例是软件系统开发的全部范围,系统用例是我们得到的最终需求。估计大部分人在绘制用例图的时候并没有认真想到过采用用例这一元素建模,实际上正在做的事情是划定开发范围,确定系统需求。
如果说业务用例是客户业务视角的话,从现在开始,系统用例将采用系统视角来看待了。
【用例实现】
现实情况中绝大部分项目都是做完用例模型后,直接开始进入数据库表设计、类设计等。
其实用例实现正是连接起用例模型和系统实现之间的桥梁。
3.4 边界
小节大纲
- 边界决定视界
- 边界决定抽象层次
- 自顶向下 or 自底向上
- 灵活使用边界
经过对参与者和用例的学习,读者应该已经体会到参与者、用例和边界相生相克的性质
边界本质上是面向对象方法的一个很重要的概念,与封装的概念师出同源。
所以在需求出来之前,我们必须先设想一个边界,这个边界的大小是不确定的,随着需求的明确,边界也逐步变得明朗。
【边界决定视界】
边界是可大可小的,由建模者主观臆定。
所以在建模过程中,如果对建模结果感到疑惑,就可以试着改变边界设定,得到不同的参与者和用例,再通过相互印证的方式得到更好的结果。
【边界决定抽象层次】
一辆汽车有几十万个零部件,我们怎么把汽车说清楚呢?我们可以通过设定边界、组织边界大小来决定抽象层次,层层推进地把汽车描述清楚。比方首先设定边界是整辆汽车,这时我们必然站在汽车外面,观察到的是汽车的大小、颜色、质地等这些东西;下一步将边界设定在汽车内部,我们观察到的是方向盘、仪表盘、中控板这些东西;再下一步,可以将边界设定在仪表盘内部,我们观察到的就是指示盘、电路、齿轮这些东西了。
勿须讳言,这是一种自顶向下的方式。也就是说,通过逐步缩小边界进而影响到我们可以观察到的事物,也就决定了我们的抽象层次,使得我们的分析粒度可以有条不紊地逐步细化。
or 自底向上。
比如有一个大系统,其涉及的单位包括商业网站、银行、政府机构、工厂、物流、批发商、零售商等,显然这里面的业务是非常错综复杂的。我们可以把边界设定为整个商业过程,得到的参与者就是商业网站、银行、政府机构、工厂等,进而得到商业网站→宣传商业信息、银行→管理财务、政府机构→监管市场、工厂→生产商品等这些抽象层次非常高的用例。为这些用例建模、获取领域模型、建立业务架构等工作,确保参与者都能达到其业务目标,整个商业模式得以实现。再接下来,把边界缩小到宣传商业信息领域,也就是商业网站部分,进而降低了抽象层次,就会得到广告策划人员、平面设计人员、网站管理员等参与者,进而得到策划广告、设计广告、发布广告等用例,而这些粒度小一些的用例保证满足宣传商业信息这一大用例。如此这般,逐层推进,直到抽象层次降低到对象的级别。
【灵活使用边界】
其实边界不仅能够在需求方面发挥作用,在设计层面也能发挥重要的作用。
这时设定一些边界就能有效地降低复杂度,比如将实现需求的任务交给分析模型,在这个边界内只考虑需求实现;将扩展能力交给框架设计,在这个边界内专心设计灵活的框架;然后再在框架的约束下把分析模型转化成设计模型。这就比在分析模型中考虑扩展能力简单得多了。
总之,边界是无形的,与其说它是一个UML元素,不如说它是一种分析方法。在面向对象的方法中,边界大到业务建模,小到接口设计都能发挥重要的作用。
:::info 一个好的分析和设计如同一筐带壳的鸡蛋,清清爽爽;一个差的设计如同一堆打碎了壳的鸡蛋,粘粘糊糊。壳,是好坏的关键。 :::
3.5 业务实体
小节大纲:
- 业务实体的属性
- 业务实体的方法
- 获取业务实体
业务实体是类(class)的一种版型,特别用于在业务建模阶段建立领域模型。业务实体是业务模型中非常重要的一个因素,它为问题领域中的关键概念建立概念化的理解,是人们认识问题领域的重要手段。
实际上,业务实体抽象出了问题领域内核心和关键的概念,如果把问题领域比喻成一幢大楼的话,业务实体就是构成这幢大楼的砖瓦和石头。
官方文档对业务实体的定义是:业务实体代表业务角色执行业务用例时所处理或使用的“事物”。
如何理解上述的定义呢?首先,业务实体是来自现实世界的,在我们建模的问题领域里一定能够找到与它相对应的事物,并且这个事物是参与者在完成其业务目标的过程中使用到的或创建出来的。
其次,业务实体一定是在分析业务流程的过程当中发现的,而业务流程实际上就是业务用例场景。这意味着业务实体必须至少被一个业务用例场景使用或创建,对业务用例场景没有贡献的事物,即使它是客观存在的,也不应当为它建模。
最后,业务实体作为类的一个版型,具有对象的所有性质,包括属性和方法,同时也具有对象的独立性,即业务实体只应当包含它本身固有的特性,而不能包含外界是如何使用它的信息。(一把刀就是一把刀)
【业务实体的属性】
属性是用来保存业务实体特征的一个记录,业务实体的属性集合决定了它的唯一性。
【业务实体的方法】
方法是访问一个业务实体的句柄,它规定了外部可以怎样来使用它。比如一台电视,它的方法就是遥控器,我们可以开、关、调声音、调频道,但是我们不可以试图让它飞起来——因为它没有这样的方法。
这种特性也是面向对象方法中对象封装的概念,回顾1.1.3面向对象方法一节,曾经谈到过对象的这样一个特点:对象是“自私”的,即便在伙伴之间,每个对象也仍然顽固地保护着自己的领地,只允许其他人通过它打开的小小窗口(这称为方法)进行交流,从不会向对方敞开心扉。
【获取业务实体】
首先我们要建立业务用例场景。业务用例场景是参与者实现其业务目标的过程描述,例如我们描述一个寄信人到邮局寄信的用例场景:寄信人到达邮局,购买信封,将信装入信封,写上地址,称重,计算邮资,购买邮票,贴上邮票,邮寄信件,拿走回执。
然后,从业务用例场景中逐个分析动词后面的名词,它们就是业务实体的备选对象。例如邮局、信、信封、地址、邮资、邮票、信件、回执等
3.6 包
包是一种容器,如同文件夹一样,它将某些信息分类,形成逻辑单元。使用包的目的是为了整合复杂的信息,某些语义上相关或者某方面具有共同点的信息都可以分包。
包是UML非常常用的一个元素,它最主要的作用就是容纳并为其他元素分类。包可以容纳任何UML元素,例如用例、业务实体、类图等,也包括子包。在Rose中我们可以看到默认的三个顶级包:Use Case View、Logic View和Component View,在其下可以按需要建立无限层次的分包。
名词解释1:什么是依赖?如果A事物发生变化,B事物必然变化,我们称B依赖于A;反之则无依赖关系。
【分包的指导性原则】
- 被分入同一个包的元素联系紧密,不可分割,具有相同性质。
- 修改一个包元素,其他包不受影响。
- 不能解除依赖关系的情况下,要保证依赖关系不会传递。
- 控制包之间的依赖关系是单向的,避免双向依赖和循环依赖。
- 领域包
- 子系统
- 组织结构
- 层
包之间的依赖关系应当是单向的,应当尽量避免双向依赖和循环依赖。如果A依赖于B,而B又依赖于A,我们称这是一种双向依赖关系;如果A依赖于B, B依赖于C,而C又依赖于A,我们称这是一种循环依赖关系。双向依赖和循环依赖都是不好的分包。
3.7 分析类
小节大纲:
- 边界类
分析类说起来也很简单,加起来总共也只有三个,分别边界类(boundary)、控制类(control)和实体类(entity),这些分析类都是类(class)的版型。
【边界类】
边界类是一种用于对系统外部环境与其内部运作之间的交互进行建模的类。
对现实世界来说,边界类的实例可以是窗口、通信协议、打印机接口、传感器、终端等,在计算机世界里,边界类也可以是一个消息中间件、一个驱动程序、一组对象接口甚至任意的一个类。
边界类的常用场景:
- 参与者与用例之间应当建立边界类。
- 例如,参与者通过一组网页、一组Windows窗口、一个字符终端或者是一只鼠标来使用用例的功能,上述的东西都可以称为用例的边界类。
- 用例与用例之间如果有交互,应当为其建立边界类。
- 一个用例如果要访问另一个用例,直接访问用例内部对象是不好的结构,这样将导致紧耦合的发生。而边界类可以隔离这种直接访问,其作用相当于一个门面模式。在最终实现时,用例之间的边界类可以演化为一组API、一组JMS消息或是一组代理类。
- 如果用例与系统边界之外的非人对象有交互,例如第三方系统,应当为其建立边界类。
- 这通常是因为异构系统、异构数据、访问权限、安全通道等原因。在具体实现时,边界类可以演化为中介和通信协议,中介的例子如网关、通信中间件、代理服务器、安全认证服务器、WebService、SOA组件等;通信协议的例子如HTTP、FTP、SSL、RMI、SOAP等。
- 在相关联的业务对象有明显的独立性要求,即它们可能在各自的领域内发展和变化,但又希望互不影响时,也应当为它们建立边界类。
- 在实现时,边界类可以转化为一组接口来为这些对象解耦。
- 从架构角度上来说,边界类主要位于展现层。边界类的获取对架构设计中的展现层有着重要的指导意义。
一个好的边界类应该具有的特点:
- 边界类有助于提高系统可用性
- 边界类保持在较高的层次(如概念层次)
- 边界类应该合理封装介于系统与主角之间的交互。
- 如果主角改变他们为系统提供输入的方式,边界类就应该是唯一需要改变的对象。
- 如果系统改变为主角提供输出的方式,边界类就应该是唯一需要改变的对象。
- 边界类必须“知道”其他对象类型(例如控制对象和实体对象)的需求,以便它们能够得以实施,并相对于“系统内部元素”保持其可用性和有效性。
【控制类】
定义:控制类用于对一个或几个用例所特有的控制行为进行建模。
在UML的定义中,认为控制类主要起到协调对象的作用,例如从边界类通过控制类访问实体类,或者实体类通过控制类访问另一个实体类。但是UML的定义也认为不必强制使用控制类,例如边界类也可以直接访问实体类。
虽然从理论上讲可以如此,但在实践中笔者认为应当强制使用控制类,因为这是一种好的程序结构。边界类直接访问实体类的例子便是早些年前的C/S结构应用模式,以及网页+数据库的应用模式,通常都是两层架构应用。实践证明这并非好的应用模式,业务逻辑代码要么与显示混在一起(网页里充满了处理代码),要么与数据逻辑混在一起(大量的存储过程)。
我们应当养成习惯,在边界类和边界类、边界类和实体类、实体类和实体类之间都默认加入控制类,将相关的处理逻辑放到控制类里去,哪怕该控制类只有一个操作。
在设计阶段,控制类可以被设计为Session Bean、COM+、Server Let、Java类、C++类等设计类。从架构角度上来说,控制类主要位于业务逻辑层。控制类的获取对架构设计中的业务逻辑层有着重要的指导意义。
【实体类】
定义:实体类是用于对必须存储的信息和相关行为建模的类。实体对象(实体类的实例)用于保存和更新一些现象的有关信息,例如,事件、人员或者一些现实生活中的对象。
特性:
- 永久性,属性和关系长期需要,甚至在系统的整个生存期都需要。
实体类源于业务模型中的业务实体。
在设计阶段,实体类可以被设计为Entity Bean、POJO、SDO、XML Bean等设计类甚至是一条SQL语句。从架构角度上来说,实体类主要位于数据持久层。实体类的获取对架构设计中的数据持久层有着重要的指导意义。
【分析类的三高】
分析类的抽象层次有三高的特点,正是因为这些特点,使得分析类成为比设计类“更好用”的元素,也是作者喜欢使用分析类的最重要原因。分析类的三高分别是:
- 高于设计实现
- 以实体类为例,一个实体类可以被设计成Entity Bean,也可以被设计为POJO,不论是哪一种设计实现,都要遵循相关的规范,实现特定的接口等。这些复杂的要求在为需求考虑系统实现的时候就成为一些杂音,要处理的信息越多,越容易分散注意力。
- 高于语言实现
- 高于语言实现意味着,在为需求考虑系统实现的时候,可以不理会采用哪一种语言来编写代码,也就可以排除特定语言的语法、程序结构、编程风格和语言限制等杂音,而能专注在需求实现上。
- 例如,Java不允许多继承。如果分析时连实现语言的细节也要考虑进去,就会浪费很多时间。而对于分析类来说,我们只需要表示出类的职责即可,不必理会实现语言的约束。
- 高于实现方式
- 高于实现方式意味着,在为需求考虑系统实现的时候,可以不考虑采用哪一种具体的实现方式。
- 例如安全认证,可能的实现方式有LDAP、CA认证、JAAC等,如果在进行需求分析时就开始考虑这些实现方式,一方面会付出过多的精力,另一方面考虑过多的具体细节相反会扰乱需求实现的分析工作。
可以看到,一方面由于分析类的抽象层次较高,基本上停留在“概念”阶段,相对于设计实现、语言实现、实现方式这些较低抽象层次的工作来说,需要考虑的信息量要少得多,而能够让分析工作专注在实现需求上。因为相对于设计模式、编程风格这些因素来说,忠实地实现需求才是项目成功的第一位。
—稳定和易变—
另一方面,也由于分析类的抽象层次较高,概括能力就很强,也就比设计和实现要稳定。在一个演进式的软件生命周期里,维护稳定的分析类比维护易变的设计类要投入更少的精力,更容易获得一个稳定架构来指导整个软件的开发。
3.8 设计类
不过,系统分析最终还是要落实到实现上的,这一节就来学习设计类,将分析结构转换成实现的元素。
设计类是系统实施中一个或多个对象的抽象;设计类所对应的对象取决于实施语言。设计类用于设计模型中,它直接使用与编程语言相同的语言来描述。
到了这个阶段,设计类已经直接映射到实现代码了,因此设计类依赖于实施语言。
作为统一语言,UML还是为设计类的概念进行了定义:设计类由类型、属性和方法构成。设计类的名称、属性和方法也直接映射到编码中相应的class、property和method。
不过在本书讲述UML的过程中,是假设不涉及到具体语言的,只对设计类的通用概念进行讲解。
【类】
类对对象进行定义,而对象又实现(或称为实施)用例。
在Java和C++这些典型的面向对象语言里,类就对应于一个class声明。
类是对对象某一方面特征的归纳和抽象,而对象则是类实例化的结果。例如小汽车、公共汽车、大卡车,从用途的角度可以抽象出的类为交通工具,反之,小汽车是交通工具的一个实例。
实际的工作中,设计类的获得很多时候可以参照某个软件框架的指导或某个规范的要求。例如采用J2EE作为软件架构时,servlet、sessionbean、entity bean等就是必须遵守的规范,根据规范我们可以从分析类中抽象出设计类。
【属性】
属性是对象特征,属性同时表明了对象的唯一性。属性名称是一个名词,描述与对象有关的属性的角色。创建对象时,属性可以具有初始值。
【方法】
原则上,访问对象或影响其他对象的属性或关系的唯一途径就是方法,直接访问和修改对象的属性是不提倡的。对象的方法由它的类进行定义。绝大多数情况下,类定义的方法都是由实例化后的对象来执行的,即这些方法为对象方法;但有时候方法也可以由类来执行,这种方法称为类方法。例如,在Java中,类方法是由static关键字声明的,一个static方法可以直接由类来执行而不必实例化成对象。
方法的作用是访问和改变对象的属性,有时候方法仅仅封装了算法,执行该方法不会改变对象的属性。在面向对象中,需要注意的原则是一个对象的属性只应该由它自己的方法来改变。
【可见性】
在UML中,可见性被归纳为以下四类:
- 公有:除了类本身以外,属性和方法对其他模型元素也是可视的。
- 保护:属性和方法只对类本身、它的子类或友元(取决于具体语言)是可视的。
- 私有:属性和方法只对类本身和类的友元(取决于具体语言)是可视的。
- 实施:属性和方法只在类本身的内部是可视的(取决于具体语言)。实施可见性是私有可见性的变体。
UML对可见性的定义在具体语言中是有一些差别的。
3.9 关系
在UML中,关系是非常重要的语义,它抽象出对象之间的联系,让对象构成某个特定的结构。本节将列举出UML所定义的关系,并解释它们的语义。
【关联关系(association)】
关联关系是用一条直线表示的,如A —— B。
它描述不同类的对象之间的结构关系,它在一段时间内将多个类的实例连接在一起。关联关系是一种静态关系,通常与运行状态无关,而是由“常识”、“规则”、“法律”等因素决定的,所以关联关系是一种“强关联”的关系。
例如,公司与员工之间一对多就是一种符合“常识”的关系;乘车人和车票之间的一对一关系是符合“规则”的关系;公民和身份证之间的一对一关系是符合“法律”的关系。
关联关系用来定义对象之间静态的、天然的结构。这与依赖关系是不同的,依赖关系表达的是对象之间临时性的、动态的关系。
关联关系具有多重性,常见为一对一关联、一对多关联、多对多关联等,也可以是任意多重性关联,如对关联(*代表任意数)。
关联关系一般不强调关联的方向,当A——B时,我们默认为A和B都相互“知道”对方的存在。大多数情况下,这也是适当的。但如果特别强调了关联的方向,如A——>B,那么表示的是A“知道”B,但B不知道A。
特别的,在用例模型中,单向关联关系用于连接参与者和用例,箭头由参与者指向用例,表示参与者“知道”用例的存在。
[依赖关系(dependency)]
依赖关系是用一条带箭头的虚线表示的,如A——->B(A依赖于B)。
它描述一个对象在运行期会使用到另一个对象的关系。与关联关系不同的是,依赖关系是一种临时性的关系,它通常都是在运行期产生,并且随着运行场景的不同,依赖关系也可能发生变化。
例如人和船这两个对象,如果运行场景是开动轮船,那么轮船依赖于人(水手);如果场景变为渡海,那就变成人依赖于船了。可见,依赖关系是一种“弱”关系,它不是天然存在的,并且会随着运行场景的变化而变化。
人和刀这两个对象,平时它们是没有关系的,但在削苹果这个场景里,人依赖于刀;脱离了这个场景,或者说当场景结束后,依赖关系也就不存在了。
一般而言,依赖关系在最终的代码里体现为类构造方法、类方法等的传入参数。与关联关系相比,依赖关系除了临时“知道”对方外,还会“使用”对方的属性或方法。从这个角度讲,被依赖的对象改变会导致依赖对象的修改。
举例来说,A对象保存了B对象的实例,但A对象对B对象没有操作,这时A仅仅是“知道”B对象,应当用关联关系,并且B修改了方法以后,A并不会变化;但如果A对象在某个场景当中使用了B对象的属性或方法,则B的修改会导致A的修改,这时A依赖于B。
【扩展关系(extends)】
扩展关系是用一条带箭头的虚线加版型<
一般来说,扩展用例是带有抽象性质的,它表示了用例场景中的某个“支流”,由特定的扩展点触发而被启动。所以严格来说扩展用例应当用在概念用例模型中,通过分析业务用例场景抽象出关键的可选核心业务而形成扩展用例。
与包含关系不同的是,扩展表示的是“可选”,而不是“必需”,这意味着:
- 即使没有扩展用例,基本用例也是完整的;
- 如果没有基本用例,扩展用例是不能单独存在的;
- 如果有多个扩展用例,同一时间用例实例也只会使用其中的一个。
在建模过程中,我们使用扩展关系可能基于以下理由:
- 表明用例的某一部分是可选(或可能可选)的系统行为。这样就可以将模型中的可选行为和必选行为分开。
- 表明只在特定条件(有时是例外条件)下才执行分支流,如触发警报。
- 表明可能有一组行为段,其中的一个或多个段可以在基本用例的扩展点处插入。所插入的行为段(以及插入的顺序)将取决于在执行基本用例时与主角进行的交互。
- 表明多个基本用例中都有可能触发一个可选的分支流。从这个意义上说,扩展用例也代表了多个用例的可复用部分。
为了理解扩展关系,让我们来看一个例子。在打电话时,如果在通话过程中收到另一个呼叫,我们可以将当前通话保留而接听另一个通话。在这个场景中,保留通话用例就是打电话用例的一个扩展用例。我们可以看到,是否需要保留通话取决于打电话人的决定,而不是必需,即使我们没有使用保留通话功能,也不影响打电话的完整性。但是如果没有之前的打电话用例,也就不可能单独启动所谓的保留通话用例了。
【包含关系(include)】
包含关系是用一条带箭头的虚线加版型<
包含用例是被封装的,它代表可在各种不同基本用例中复用的行为。因此,与扩展用例一样,包含用例也应当用在概念用例模型中,通过分析业务用例场景而抽象出关键的必选的核心业务而形成包含用例。
与扩展用例不同的是,包含用例表示的是“必需”而不是“可选”,这意味着如果没有包含用例,基本用例是不完整的,同时如果没有基本用例,包含用例是不能单独存在的。
建模时包含关系基于以下理由:
- 从基本用例中分解出这样的行为:它对于了解基本用例的主要目的并不是必需的,只有它的结果才比较重要。
- 分解出两个或更多个用例所共有的行为。
为了理解包含关系,让我们来看一个例子。去银行办理业务,不论是取钱、转账还是修改密码,我们都需要首先核对账号和密码,因此可以将核对账号作为上述业务用例的共有行为提取出来,形成一个包含用例。
【实现关系(realize)】
实现关系是用一条带空心箭头的虚线表示的,如AB(A实现B)。它特别用于在用例模型中连接用例和用例实现,说明基本用例的一个实现方式。
实现所代表的含义是,基本用例描述了一个业务目标,但是该业务目标有多种可能的实现途径,每一种实现途径可以用用例实现(或称用例实例)来表示,而用例实现与基本用例之间就构成了实现关系。换言之,每个实现途径都实现了基本用例的业务目标。
我们用如图3.28所示的交纳电话费业务作为例子,可以看到,交纳电话费是一个业务目标,其实现途径可能有营业厅交费、银行交费、预存话费等,每一个用例实现都是同一业务目标的不同实现过程,因此它们之间是实现的关系。
【精化关系(refine)】
精化关系是用一条带箭头的虚线加版型<
精化关系也可以用于模型与模型之间,表示某个模型是通过精化另一个模型而得来的。比如说,我们认为设计类是通过精化分析类而得来的,我们可以用XX设计类<
与泛化关系不同的是,精化关系表示由基本对象可以分解为更明确、精细的子对象,这些子对象并没有增加、减少、改变基本对象的行为和属性,仅仅是更加细致和明确化了。在泛化关系中,基本对象被泛化成为子对象后,子对象继承了基本对象的所有特征,并且子对象可以增加、改变基本对象的行为和属性。
另一方面,精化关系仅仅用于建模阶段,在实现语言中是没有精化这一语义的。泛化则等同于实现语言中的继承语义。
我们讲到概念模型是用于获取业务模型中的关键概念的,从业务模型中分析出实现业务目标的那些核心行为和实体,从而描述出一个关键的业务结构以得到一个易于理解的业务框架。这些关键概念就是对业务用例的精化。它们表示为概念用例到业务用例的精化关系。
我们讲到概念模型是用于获取业务模型中的关键概念的,从业务模型中分析出实现业务目标的那些核心行为和实体,从而描述出一个关键的业务结构以得到一个易于理解的业务框架。这些关键概念就是对业务用例的精化。它们表示为概念用例到业务用例的精化关系。
【泛化关系(generalization)】
泛化关系是用一条带空心箭头的直线表示的,如AB(A继承自B)。泛化关系可用于建模过程中的任意一个阶段,说明两个对象之间的继承关系。
特别需要说明的是,作者并不赞同在用例之间使用泛化关系,尽管UML认为它是合法的。
【聚合关系(aggregation)】
聚合关系是用一条带空心菱形箭头的直线表示的,如AB(A聚合到B上,或者说B由A组成)。
聚合关系用于类图,特别用于表示实体对象之间的关系,表达整体由部分构成的语义。例如一个部门由许多人员构成。
与组合关系不同的是,整体和部分不是强依赖的,即使整体不存在了,部分仍然存在。例如部门撤销以后,人员不会因此而消失,他们依然存在。
【组合关系(composition)】
组合关系是用一条带实心菱形箭头的直线表示的,如AB(A组合成B,或者说B由A构成)。
组合关系用于类图,特别用于表示实体对象关系,表达整体拥有部分的语义。例如母公司拥有许多子公司。
组合关系是一种强依赖的特殊聚合关系,如果整体不存在了,则部分也将消亡。例如母公司解体了,子公司也将不再存在。
3.10 组件
组件是系统中实际存在的可更换部分,它实现特定的功能,符合一套接口标准并实现一组接口。组件代表系统中的一部分物理实施,包括软件代码(源代码、二进制代码或可执行代码)或其等价物(如脚本或命令文件)。
建模过程中,我们通过组件这一元素对分析设计过程中的类、接口等进行逻辑分类,一个组件表达软件的一组功能。例如一个网站有用户注册和用户维护两个目标功能,通过对网站需求的用例分析和设计,我们得到许多类和接口,这些类和接口实现网站的用户管理。出于构件化的需要,我们把那些紧密合作的类和接口组合起来实现一组特定的功能,形成一个组件。
UML中把组件定义为任何的逻辑代码模块。笔者认为这样的定义太过于随意,相反失去了组件的意义。按照笔者的理解,一个组件应当具有完备性、独立性、逻辑性和透明性。
在UML的定义中,组件之间唯一的关系就是依赖,在Rose中,组件视图中允许的唯一连接也是依赖关系,而依赖意味着一个组件的修改会导致依赖于它的其他组件的修改。
但是在笔者看来,一个组件应当是一个独立的业务模块,有着完备的功能,可独立部署,一个组件可以看成是一个完备的服务。从SOA架构的观点来看,一个SOA服务与其他服务是没有依赖关系的,服务与服务之间仅仅保持着松耦合的通信关系。
笔者觉得,组件之间仅仅应当保持关联关系,甚至连关联关系都没有。它们之间是通过架构来沟通的,即松耦合,在发出消息之前,组件之间甚至不知道对方的存在。
【完备性】
完备性是说,组件包含一些类和接口,一个组件应当能够完成一项或一组特定的业务目标(或说功能)。从调用者的观点看,它不需要调用多个组件来完成一个业务请求。
例如我们将组件A定义为用户注册,那么我们应该在组件A中包含所有实现用户注册的必需的类和接口,在任何时候,仅通过组件A就可以注册一个用户而无须访问组件外的其他类。
【独立性】
独立性是说,组件应当是可以独立部署的,与其他组件无依赖关系,最多仅保持关联关系。例如可以把组件A部署到服务器1,把组件B部署到服务器2,虽然组件A和B都共同使用用户数据,但是A与B之间无依赖关系。也就是说,组件与组件之间应当是松耦合关系。
【逻辑性】
逻辑性是说,组件是从软件构件设计的观点来定义的,并非从需求中可以直接导出。组件建立在系统分析和设计的基础上,对已经实现的功能进行逻辑划分。组件的定义是为了规划系统结构,将一个复杂的系统分解为一个个具有完备功能的、可独立部署的小模块。这些小模块可大可小,从理论上说,可任意选择一部分功能定义一个组件。
【透明性】
透明性是说,组件的修改应当只涉及组件的定义以及组件中所包含的类的重新指定,而不应当导致类的修改。例如当一个组件的功能变化时,它所包含的类可能从原来的类A、类B、类D变成类B、类C、类D,但是类A、B、C、D都不应当被修改。
【使用组件】
组件是从系统结构的角度来划分分析设计的结果的。在实际工作中,笔者的经验是遇到以下情况时,组件比较有用。
- 分布式应用。
- 在分布式应用的情况下,系统的功能可能被部署在异构环境下,一个业务目标可能需要经历两个甚至多个节点才能完成。这时我们需要将实现业务目标的那些类和接口规划成一些组件,每个组件完成这个业务目标中的一部分功能。这些组件可被独立部署在不同的节点上,相互之间通过既定的通信协议交互来完成业务目标,如图3.30所示。
- 应用集成
- 在应用集成项目中,经常面临新业务和遗留系统问题。新业务需要调用遗留系统的功能,但是又不能修改遗留系统。原因可能是修改遗留系统的代价高昂,也可能是结构差异导致新旧系统无法直接通信。不管什么原因,为了保证遗留系统能够被集成到新系统中,一个解决方案就是在新系统中规划出一些组件,这些组件所拥有的接口完成遗留系统的功能。新系统是的其他业务模块与这些组件交互,而这些组件则拥有遗留系统的代码或者通过某种方式(代理模式、适配置器等)使用遗留系统,如图3.31所示。
- 第三方系统
- 第三方系统如果在建设的项目中,有第三方系统要访问本系统,出于松耦合的考虑,让第三方系统直接使用或者说把本系统中的类直接暴露给第三方系统是很糟糕的设计。因此,有必要将本系统要提供给第三方系统使用的功能定义成一系列组件,让第三方通过组件来访问本系统。在这些组件中,除了包含本系统的实现类外,还可以根据实际情况通过提供这些实现类的代理、适配器、消息中间件等手段来解耦第三方系统对本系统的依赖,如图3.32所示。
- SOA服务
- SOA(Service Oriented Architecture)面向服务的架构是目前新兴的软件架构,有人说SOA是下一代软件的发展趋势。它将系统结构划分为粗粒度的服务组件SCA,每个服务组件都遵循一系列的标准和规范,通过标准的通信协议与其他服务交互,服务与服务之间是松耦合的。在SOA中,系统分析、设计、开发都以服务为主,每个服务都具有上述组件的所有特点。
- 实际上组件的概念非常类似于SOA的服务。如果要开发一个SOA架构的应用系统,那么开发SOA服务的过程实际上就是定义组件的过程。在SOA架构下,系统功能由一个个的服务向外部暴露,也就是说,系统被定义成一个个的组件。这些服务是松耦合的,它们之间通过企业总线交互以完成业务功能,如图3.33所示。
:::info 组件一般都是在较高的抽象层次定义的,在许多应用项目中并不需要组件建模。但是,如果采用了组件化的开发架构,或者从一开始就决定采用组件化开发模式,那么从系统分析开始就应当着手建立组件模型,并在后续的模型中逐步精化。 :::
3.11 节点
节点是带有至少一个处理器、内存以及可能还带有其他设备的处理元素。
- 在实际工作中,一般说来服务器、工作站或客户机都可以称为一个节点。
- 节点是应用程序的部署单元。
- 节点元素特别用于部署视图,描述应用程序在物理结构上是如何部署在应用环境中的,是一种包括软、硬件环境在内的拓扑结构描述。
UML只定义了节点和设备两个元素,即使通过文字来命名节点和设备,看上去还是显得太过于单一。因此,笔者建议可以使用其他绘图工具,如Visio等绘制各种拓扑结构图来代替部署视图,至少在视觉效果上比较生动。
一般来说,以下两种情况下需要使用到节点元素。
【分布式应用环境】
在分布式应用环境中,通常会有多于一个的服务器、处理设备或者中间件。所开发出的应用程序会部署到这些不同的服务器或处理节点上,通过描述这些服务器之间的调用和依赖关系以表达应用环境的拓扑结构。图3.34展示了一个客户服务分布式应用系统的节点拓扑视图。
【多设备应用环境】
如果应用环境中包括多种硬件设备,为了表达这些硬件设备的结构,应当使用节点元素来绘制部署视图。图3.35展示了ATM应用环境中的节点和设备结构,它来自统一过程的官方文档。
4. UML 核心视图
元素是UML的基本词汇,那么视图就是语法,UML通过视图将基本元素组织在一起,形成有意义的句子。
本章学习视图,内容包括用例图、类图、包图等静态视图及活动图、状态图、时序图和协作图等动态视图。
UML可视化的特性是由各种视图来展现的,每一种视图都从不同的角度对同一个软件产品的方方面面进行展示,说明将要开发的软件到底是什么样子。描述软件和描述现实世界一样,一方面我们需要描述系统的结构性特征,结构决定了这个系统能做什么;另一方面我们需要描述系统的运行时行为,这些行为特征决定了系统怎么做。两者结合起来才能把系统描述清楚。
UML 里,静态视图表达结构性特征,动态视图表达行为性特征。
4.1 静态视图
静态视图就是表达静态事物的。它只描述事物的静态结构,而不描述其动态行为。我们将要介绍的静态视图包括用例图、类图和包图。
4.1.1 用例图
用例视图采用参与者和用例作为基本元素,以不同的视角展现系统的功能性需求。
用例视图是了解系统的第一个关口,人们通过用例视图得知一个系统将会做什么。对客户来说,用例视图是他们业务领域的逻辑化表达,对建设单位来说,用例视图是系统蓝图和开发的依据。
建模者通过用例视图将获得的参与者和用例从某个角度进行展示,表达软件某个方面的视角。一般来说,有以下用例视图:
- 业务用例视图:业务用例视图使用业务主角和业务用例展现业务建模的结果。
- 业务主角视角
- 业务模块视角
- 其他视角:比如部门视角,业务实体生命周期视角
- 业务用例实现视图:业务用例视图使用业务主角和业务用例展现业务建模的结果。
- 实际工作中,如果一个业务用例只有一个实现途径,那么绘制业务用例实现视图似乎不是那么必要,有点多此一举。
- 笔者建议,无论是否有多种实现方式,绘制业务用例实现视图都是一个好习惯,是符合软件工程需求可追溯原则的好的做法
- 概念用例视图:概念用例视图用于展现从业务用例中经过分析分解出来的关键概念用例,并表示概念用例和业务用例之间的关系。一般来说这些关系有扩展、包含和精化。
- 概念用例视图不是必需的,如果业务用例是一个复杂的业务,绘制概念用例视图有助于细化和更准确地理解业务用例。
- 系统用例视图:系统用例视图展现系统范围,将对业务用例进行分析以后得到的系统用例展现出来。
- 它表达的含义是计算机系统将开发本视图中所列举出来的系统用例,而检查借阅证可能是手工工作而不需要纳入系统建设范围。
- 系统用例实现视图:与业务用例实现视图类似,如果一个系统用例有多种实现方式,也应当为其绘制实现视图。
- 笔者还是建议为即使只有一种实现方式的系统用例也绘制实现视图。这是因为系统用例的实现视图本身是一种可扩展的框架,当将来业务变化,需要增加一种实现方式时只需再增加一个系统用例实现而不需要修改原有的实现。
实际项目可适当裁剪,只保留业务用例视图和系统用例视图。但要知道用例图在不同生命周期阶段有着不同的表达。
4.1.2 类图
类图用于展示系统中的类及其相互之间的关系。
本质上说,类图是现实世界问题领域的抽象对象的结构化、概念化、逻辑化描述。
实际上,UML解决面向对象的困难的方法源于面向对象方法中对类理解的三个层次观点,这三个层次是概念层、说明层和实现层。
类图建模是先概念层而说明层,进而实现层这样一个随着抽象层次的逐步降低而逐步细化的过程。
【概念层类图】
概念层的观点认为,在这个层次的类图描述的是现实世界中问题领域的概念理解,类图中表达的类与现实世界的问题领域有着明显的对应关系,类之间的关系也与问题领域中实际事物的关系有着明显的对应关系。
在概念层上,类图着重于对问题领域的概念化理解,而不是实现,因此类名称通常都是问题领域中实际事物的名称。概念层的类图是独立于实现语言和实现方式的。
概念层类图位于业务建模阶段。通常在这个阶段类图是以领域模型图,即业务实体图来表示的。
该图展示了网上购物的业务实体图,网上购物主要由商品、定单、支付卡这几个关键类构成,这几个类的交互能够完成网上购物这个业务目标。
【说明层类图】
说明层类图是搭建在现实世界和最终实现之间的一座桥梁。在这个阶段,类通常都非常粗略,虽然它表达了计算机的观点,但是在描述上却采用了近似现实世界的语言,以保证从现实世界到代码实现的过渡。
图4.8展示了网上购物的分析类图,这个类图表达了从计算机的视角来说,网上购物这个业务目标是由哪些类来完成的,这些类的接口保证了这个业务目标的达成。
【实现层类图】
实现层观点认为,类是实现代码的描述,类图中的类直接映射到可执行代码。在这个层次上,类必须明确采用哪种实现语言、什么设计模式、什么通信标准、遵循什么规范等。
实现层的类图大概是用得最普遍的,许多人在建模的时候根本没有概念层和说明层的类图而直接跳到实现层类图。原因不是他们确认对问题领域已经足够了解,并且设计经验十分丰富,而通常是因为他们不知道类图还有三个层次的观点。
实现层类图位于设计阶段。在这个阶段,类图可视为伪代码
图4.9展示了J2EE架构实现查询商品功能的类图。可以看到,到了实现层类图,类描述和类关系已经是伪代码级别了。
:::info 类图在不同的软件生命周期也有三种不同的表达。 :::
4.1.3 包图
包图一般都用来展示高层次的观点。
在实际项目中,建模过程中获得的元素是非常多的,如果要将这些元素的关系都绘制出来,将如同蜘蛛网一样难以辨别。通过包这个容器来从大到小、从粗到细地建立关系是一种很好的办法。
例如图4.10展示了网上购物的领域包图,它表达了关键业务领域及其依赖关系。
图4.11展示了查询商品功能的类层次,它表达了实现类位于哪个层次的软件架构的观点。
应当学会在不同软件生命周期阶段选择适合的静态图来表达软件观点。
4.2 动态视图
动态视图是描述事物动态行为的。
动态视图不能够独立存在,它必须特指一个静态视图或UML元素,说明在静态视图规定的事物结构下它们的动态行为。
常见的动态视图包括活动图、状态图、时序图和协作图。
4.2.1 活动图
活动图描述了为了完成某一个目标需要做的活动以及这些活动的执行顺序。UML中有两个层面的活动图,一种用于描述用例场景,另一种用于描述对象交互。
:::info 争议:很多人担心面向过程的活动图引入会导致面向对象的类职责的混乱,这种担心是有道理的。
在面向对象的眼中是没有业务流程这种东西的,所谓流程只不过是在某个外部力量推动下对象之间相互交流的一个过程,它只是“瞬时”的。
我们面临着这样一个矛盾,既要保持面向对象观点中对象的独立性,又要保持现实世界中业务目标的过程化描述。 :::
我们使用活动图来描述用例场景,帮助我们认识问题领域,从问题领域中发现关键的对象,然后就应该把活动图中的流程忘掉,而专心研究关键对象的特性。最后,再来验证一下这些关键对象的某个交互结果是否的确能够达到用例场景所描述的业务目标。
【用例活动图】
用例活动图是最经常使用的。用例表达了参与者的一个目标,用例场景则描述了如何来达到这个目标。活动图用来描述用例场景,也就是通常所说的业务流程。业务流程一般包括一个基本业务流程和一个或多个备选业务流程,而业务流程则通过多个活动按照一定的条件和顺序执行来推进。活动可以是手动执行的任务,也可以是自动执行的任务,每个活动完成一个工作单元。
在图4.12展示的活动图中有几个关键的元素,下面分别对它们进行一些解释。
- 起始点:起始点标记业务流程的开始。一个活动图仅有一个。
- 活动:活动是业务流程中的一个执行单元。有四个特定事件:entry,do,event,exit
- entry指进入(启动)活动时要执行的动作(或者类方法);
- do指活动执行过程中要进行的动作(或者类方法);
- event事件指活动在执行中接收到某个事件时执行的动作;
- exit指活动在退出(结束)时要进行的动作。
- 判断:判断根据某个条件进行决策,执行不同的流程分支。
- 同步:同步分为同步起始和同步汇合。
- 同步起始表示从它开始多个支流并行执行;
- 同步汇合表示多个支流同时到达后再执行后续活动。
- 结束点:结束点表示业务流程的终止。一个或多个。
- 基本流:基本流表示最主要、最频繁使用的、默认的业务流程分支。
- 支流:基本流表示最主要、最频繁使用的、默认的业务流程分支。
- 图中无行李分支
- 异常流:异常流表示非正常的、不是业务目标期待的、容错性的、处理意外情况的业务流程分支。
- 身份证核对错误
- 组合活动:组合活动可以用嵌套的活动来表示。不过这种方式会导致活动图太复杂而不清晰,建议不使用,宁可另外用一幅活动图来展示这些子活动。
【对象活动图】
对象活动图用于展示对象的交互。我们以4.1.2类图一节中的图4.9为例,根据查询商品的对象交互过程绘制出如图4.14所示的对象活动图。
尽管UML允许用活动图绘制对象交互,但是实际工作中实在没什么理由要使用它。因为UML有其他更好的工具来绘制对象交互图,例如接下来将要讲到的4.2.2节状态图、4.2.3节时序图和4.2.4节协作图。
【泳道】
上面的活动图描述了业务流程中活动的执行顺序,却没有描述出谁来执行这些活动,即执行业务流程的职责被遗漏了。
面向对象的分析观点里则与之相反,业务的执行过程不是重要的,对象职责才是最重要的。泳道技术的引入多多少少解决了活动图不能描述对象职责的遗憾。
泳道,顾名思义,就像一个游泳运动员只能在一个泳道里进行比赛一样,一个对象也只能在一个业务流程中担任一个(或一类)职责。泳道代表了一个特定的类、人、部门、层次等对象的职责区,这些对象在业务流程中负责执行的活动集合构成了它们的职责。
即使加入泳道后对象交互图有了些模样,笔者仍然不推荐使用它。泳道最主要的用途是在分析用例场景时用来获取角色职责。
上面我们学习了活动图的基本知识,在实际的建模过程中,活动图主要应用于业务场景建模和用例场景建模。下面分别进行讲解。
【业务场景建模】
这时,我们经常以业务主角(客户代表)作为泳道,以从业务主角处获取的业务用例作为活动来编排活动图。这种活动图对我们获取正确的业务用例和检查已经获得的业务用例有着很好的帮助。它能够:
- 帮助发现业务用例
- 如果用现有的业务用例不能完整地编排出实际的业务流程,那么可能是遗漏了业务用例。
- 帮助检查业务用例粒度
- 如果用现有的业务用例编排活动图感觉到别扭,那么可能是业务用例的粒度不统一。
- 帮助检查业务主角
- 如果有些业务主角难以编排进活动图,那么可能是业务主角定义错误。
- 帮助检查业务用例
- 如果有些业务用例在活动图中用不上,那么可能是业务用例获取错误。
【用例场景建模】
获得业务用例之后,我们得到了参与者的业务目标,我们通过用例场景来说明如何达到业务目标。
用例场景活动图的好处:
- 帮助发现概念用例
- 如果发现在多个用例场景中类似的工作单元经常出现,那么可以考虑将它抽象出来,再根据情况采用包含、扩展或者泛化的关系将其连接到基本用例(即它们所贡献的业务用例)。这些概念用例通常构成了业务架构中的关键业务,而那些仅出现一次的工作单元不需抽象成概念用例,它们通常对业务架构仅起到参与作用,不必过于关心。
- 帮助发现角色
- 同一个工作单元被多个角色使用,可以考虑抽取更高级别的角色。
- 帮助发现业务实体
- 我们会发现所有活动都有着相同的命名规则:动词+名词,这些名词就是很好的业务实体(对象)来源
- 帮助建立领域模型
- 领域模型描述那些对业务有着重要意义的业务对象。如果在同一个或多个用例场景的不同活动中发现某个名词重复出现,那么应当对这个名词给予重视。
:::info 活动图是描述用例场景最为常用的图。后续章节可以看到,虽然时序图也能完成同样的工作,但是笔者还是最喜欢用活动图来描述用例场景,因为它可以最方便地描述角色职责。 :::
4.2.2 状态图
状态图显示一个状态机。状态机用于对模型元素的动态行为进行建模,更具体地说,就是对系统行为中受事件驱动的方面进行建模。
使用状态图来说明业务角色或业务实体可能的状态——导致状态转换的事件和状态转换引起的操作。
对于类的对象所有可能的状态,状态图都显示它可能接收的消息、将执行的操作和在此之后类的对象所处的状态。
状态机主要用于描述对象的状态变化以确定何种行为改变了对象状态,以及对象状态变化对系统的影响。
可以用状态机来描述业务实体对象、分析类对象和设计类对象。
需要注意的是,状态图通常只用于描述单个对象的行为,如果要描述对象间的交互,最好采用时序图或协作图。
图4.17展示了图书业务实体的状态图。
状态图中的关键元素:
- 初始状态:状态机的起始位置,不需要事件的触发。
- 状态
- 状态是对象执行某项活动或等待某个事件时的条件。
- 在UML中状态被赋予四个特定的事件。
- entry指对象进入(激活)状态时执行的动作(或者类方法)。
- do指对象状态保持不变时持续执行的动作(或者类方法),它不会因为event而停止。
- event事件指对象接收到某个事件时执行的动作,这种动作不会导致对象状态的变化,可以通过绘制一条返回状态自身的转移来表示动作的执行结果。
- exit指状态在退出(结束)时执行的动作。
- 复合状态
- 具有子状态(或者称为嵌套状态)的状态被称为复合状态。在复合状态中子状态也可能有一个初始状态和一个终止状态。
- 转移
- 转移是两个状态之间的关系,它表示当发生指定事件并且满足指定条件时,第一个状态中的对象将执行某些操作并进入第二个状态。
- 事件
- 事件是一个特定的动作或行为,有时候也包括系统时钟之类的定时器。如果条件满足,事件的发生将触发一个转移。
- 条件:
- 条件是一个布尔表达式,当事件发生时将检查这个表达式的值。条件求值结果可能决定转移的分支,或者拒绝转移。条件有可能引用当前状态。
- 最终状态
- 最终状态表示状态机执行结束,或者对象生命周期结束。
:::info
状态图是很有用的技术,尤其在描述单个复杂对象的行为时非常有助于我们理解一个对象的行为。
适用场景:
- 仅对领域模型中最为关键的业务对象,尤其是当其在一个或多个用例场景中参与了多个活动时,才对其建模。 :::
4.2.3 时序图
时序图用于描述按时间顺序排列的对象之间的交互模式;它按照参与交互的对象所具有的“生命线”和它们相互发送的消息来显示这些对象。在时序图中包含对象和主角实例,以及说明它们如何交互的消息。
时序图描述了在参与交互的对象中所发生的事件(从激活的角度来说明),以及这些对象如何通过相互发送消息进行通信。可以为用例事件流的各种不同形式制作时序图。
以上是官方文档对时序图的定义。通常我们使用时序图来描述用例实现,通过贡献于该用例实现的对象之间的交互来说明用例是如何被对象实现的。
使用时序图来描述用例实现是一种从现实世界到对象世界的映射方法,它对我们确定对象职责和接口有着显著的作用。而对象的核心就是职责和接口。
时序图的优势:强调消息事件的发生顺序,方便阐述事件流的过程;
时序图的劣势:难以表达对象之间的关系。
类图那一节有提过类有三个层次的观点:概念层、说明层和实现层,分别对应于业务建模阶段、概念建模阶段和设计建模阶段,相应的,也可以在这三个层次上分别对业务实体对象、分析类对象和设计类对象绘制业务模型时序图、概念模型时序图和设计模型时序图。
【业务模型时序图】
业务模型时序图用于为领域模型中的业务实体交互建模,其目标是实现业务用例。
用例场景建模一节中提到,活动图可以帮助我们发现业务实体,实际上,如果之前已经有了活动图再来绘制业务实体时序图时,你会发现有迹可循,非常容易。
时序图常用的 UML 元素
- 对象
- 表示参与交互的对象。每个对象都带有一条生命周期线,对象被激活(创建或者被引用)时,生命周期线上会出现一个长条(会话),表示对象的存在。
- 生命周期线
- 生命周期线表示对象的存在,当对象被激活(创建或者被引用)时,生命周期线上出现会话,表示对象参与了这个会话
- 消息
- 消息由一个对象的生命周期线指向另一个对象的生命周期线。如果消息指到空白的生命周期线,将创建一个新的会话;如果消息指到已有的会话,表示该对象延续已有会话。
- 会话
- 会话表示一次交互,在会话过程中所有对象共享一个上下文环境。例如事务上下文、安全上下文等。
- 销毁
- 销毁绘制在生命周期线上,表示对象生命周期的终止。虽然示例图中绘制了,但销毁也没有必要强调。
消息的类型:
- 简单消息:向右的单向实线箭头
- 返回消息:源消息的返回体,非新消息。向左的单向虚线箭头表示。
- 一般来说不需要为每个源消息都绘制返回消息,一方面因为默认情况下源消息都有返回,另一方面太多的返回消息会使图变得更复杂。
- 同步消息:
- 同步消息表示发出消息的对象将停止所有后续动作一直等到接收消息方响应。同步消息将阻塞源消息对象的所有行为。同步消息最为常用,通常程序之间的方法调用都是同步消息。
- 限时消息:
- 同步消息的特殊情况。源消息对象发出消息后将等待响应一段时间,在限定时间内还没有响应时,源消息对象将取消阻塞状态而执行后续操作。
- 例如访问一个网站,在限定时间内没有响应时浏览器会显示“找不到指定网址”的信息。
- 异步消息:
- 异步消息表示源消息对象发出消息后不等待响应,而可以继续执行其他操作。异步消息一般需要消息中间件的支持,如JMS、MQ等。
绘制业务模型时序图时要注意:第一,时序图以达成业务目标为准则;第二,这个阶段处于业务阶段,使用的描述语言应当采用业务术语;第三,时序图表达的内容会对将来的分析设计带来帮助,但是相对于编码实现来讲由于太粗略而不能够作为依据。
【概念模型时序图】
概念阶段的时序图采用分析类来绘制,目标同样是实现业务用例。但是,由于分析类本身代表了系统原型,所以这个阶段的时序图已经带有计算机理解。
概念用例时序图通常是依据业务模型场景图来绘制的,它将业务模型场景用分析类重新绘制一遍,这样,既保留了实际业务需求,又得到了计算机实现的基本理念。
分析类所展示出来的已经是系统实现的原型,在设计模型阶段要做的工作就是选择适合的实现方式来实现这个蓝图。
【设计模型时序图】
设计模型时序图使用设计类作为对象绘制。目标是实现概念模型中的某个事件流,一般以一个完整交互为单位,消息细致到方法级别。
显然,在实际工作中我们很难为所有的交互都绘制时序图,那将是一个巨大的工作量。
笔者建议在设计模型阶段,只需要用框架中的关键类描述典型的交互场景即可,不需要为每一个交互都绘制时序图。
为了保证软件实现满足需求,省略了大量设计模型时序图的同时,要求有更多的概念模型时序图,这样才能保留足够的信息来说明需求与实现之间的过渡。
:::info 小结:时序图的三种应用场合是在建模过程中经常使用的动态视图。除了这些场合,在任何时候需要表达对象间的交互时,或者想分析对象的职责和接口时都可以使用时序图。
在建立软件架构时,为了说明架构中的关键对象交互场景,或者为了说明应用程序如何使用架构的编程模型,也可以使用时序图来说明。 :::
4.2.4 协作图
协作图描述了对象间交互的一种模式;它通过对象之间的连接和它们相互发送的消息来显示参与交互的对象。
协作图的建模结果用于获取对象的职责和接口。与时序图不同的是,协作图因为展示了对象间的关系,使得它更适用于获得对对象结构的理解,而时序图则更适于获得对于调用过程的理解。
如果你更在意对象间的结构关系,请选择使用协作图;如果你更在意对象交互的执行顺序,则请选择使用时序图。
【业务模型协作图】
业务模型协作图同样采用业务实体来绘制,目标也是实现用例场景。
协作图(图4.21)展示了与时序图(图4.18)同样的信息,请读者体会它们之间在表达上不同的视觉感受和蕴含的侧面意义。
协作图与时序图相比,对象间的结构一目了然,并且很容易就能知道哪些消息影响了对象(或者说对象需要提供哪些接口)。不过虽然用数字标明了消息的顺序,从图中我们还是很难看出执行的顺序,更无法了解一次完整的会话过程。协作图和时序图展示着对象不同的方面。
:::info 必杀技:协作图绘制不容易,可以使用时序图转协作图的工具,让其自动生成。 :::
协作图用到的 UML 元素:
- 对象:
- 表示参与协作的对象。对象可以指定它的类,也可以直接用空对象表示,在将来再指定它的类。
- 对象关联
- 连接两个对象,表示两者的关联。与类关系不同,协作图中的对象关联是临时关联,即只在本次交互中存在;而类关系是永久关联,例如继承关系不论在什么情况下都是存在的。
- 对象关联的可见属性:
- 域(Field)可见。表示关联的对象在交互域内是一直可见的。这有些类似于Java中的包内可见的性质。
- 参数(Parameters)可见。表示关联的对象仅在交互过程中可见,它们是通过参数传递产生关联的。
- 本地(Local)可见。表示关联的对象在本地可见。本地的概念类似于指对象在同一个JVM(Java虚拟机)或者同一个Server中,或者同一个进程中是可见的。
- 全局(Global)可见。表示关联的对象是全局可见的。全局的概念类似于指对象在整个分布式应用程序中,或者一个服务器群集中,或者整个万维网中是可见的。
- 对象关联的可见属性:
- 连接两个对象,表示两者的关联。与类关系不同,协作图中的对象关联是临时关联,即只在本次交互中存在;而类关系是永久关联,例如继承关系不论在什么情况下都是存在的。
- 消息
- 协作图中的消息与时序图中的消息定义完全一样。
- 消息序号
- 其实消息序号也是消息的一部分,这里分开讲只是为了强调。序号表明消息传递的先后顺序。
【概念模型协作图】
与时序图相同,概念阶段的协作图采用分析类来绘制,目标是实现业务用例。
【设计模型协作图】
与时序图相同,设计模型协作图使用设计类为对象来绘制。目标是实现概念模型中的某个事件流,一般以一个完整交互为单位,消息细致到方法级别。
:::info 到此为止UML核心视图中的动态视图就学习完了。在动态视图中,我们学习了活动图、状态图、时序图和协作图,这些视图各有其适用的场合。
静态视图表达事物的结构性观点,而动态视图则表达事物的行为性观点。一个好的建模,结构性和行为性缺一不可,而且要相得益彰。既要说明该事物长得像什么样子,还要说明该事物应该怎么用。
不论是静态视图还是动态视图都是建模的重要工具,熟练掌握它们除了学习基本概念之外,诀窍就是多用。这些视图不但可以用在软件建模过程中,也可以用在分析现实生活中的一些事例。只要愿意,总可以从生活中找到非常多的例子来练习。
相对于掌握工具,理解其背后的本质才是更重要的。而这些理解是只可意会不能言传的。
希望大家多学多用,达到手中无剑心中有剑的层次。
下一章,我们将开始学习UML的核心模型。预习:简单的理解,其实一个模型就是一堆有意义的静态图和动态图组合在一起,表达了一个有意义的中心思想。
我们可以这样来类比:一个模型提出了论点,静态图是论据,动态图则是论证。模型的好坏,就看各位如何写好这篇议论文了!
:::
5. UML 核心模型
说模型提出了论点,静态图是论据,动态图则是论证,建立模型的过程,就是采用论据来论证论点的过程。
本章将要讲解的模型包括:■ 业务用例模型■ 概念用例模型■ 系统用例模型■ 领域模型■ 分析模型■ 软件架构和框架模型■ 设计模型■ 组件模型■ 实施模型
5.1 用例模型概述
用例模型在统一过程中占据十分重要的地位。■ 它是面向对象软件过程的骨架——开发过程中一切工作的组织框架;■ 它是面向对象软件过程的神经系统——用例驱动过程;■ 它也是面向对象软件过程的血肉——需求的来源,测试的依据……
我们谈到过用例有三个层次解释:业务用例、概念用例、系统用例,自然地,用例模型也就有业务用例模型、概念用例模型和系统用例模型三个层次的模型,如图5.2所示。
5.2 业务用例模型
5.2.1 业务用例模型主要内容
- 业务用例视图:包括业务主角和业务用例,是业务的高层和概要视图,并作为其他建模要素的组织点存在。
- 业务用例场景:说明用例的执行过程
- 业务用例规约:说明使用者、目标、场景、相关业务规则,相关业务实体。
- 业务规则:客户执行其业务必须遵守的法律法规、惯例、各种规定、操作规范、约束、机制。
- 业务对象模型:关键业务对象,如何贡献于业务目标。
- 业务用例实现视图:
- 业务用例实现场景:
- 包图:组织业务用例。可以按业务模块或业务主角分包。
5.2.2 业务用例模型工件的取舍
5.2.3 何时使用业务用例模型
但是,业务用例模型是针对商业组织建模的,并不是所有的软件都需要从业务用例建模开始。
使用业务用例模型的理由:■ 你将开发一个针对商业组织的软件。■ 你将开发一个交互密集型软件。■ 你将开发一个较大规模的软件。■ 你所面对的问题领域有复杂的组织结构。■ 你所面对的业务有许多业务流程。■ 客户希望借信息化过程进行业务重组或优化。■ 你对这个行业的业务了解不多,因而希望首先对业务有清楚的认识。■ 你希望借由一个软件开发而打入一个行业应用软件市场。■ 虽然已经对这个行业的业务了如指掌,但你希望做行业标准,因而想要建立业务架构。■ 客户已有许多孤立的遗留系统,希望做应用整合。
:::info 业务用例模型描述了业务需求,系统用例模型描述了系统需求。在从业务需求到系统需求的转化过程中,概念用例模型可以起到非常好的过渡,尤其是面对复杂业务的时候。 :::
5.3 概念用例模型
概念用例模型位于先启阶段,有时在精化阶段进行,是业务用例建模的一个子集。
这时,我们需要一种方法来“分解”那些较大的业务用例,从中找到关键和核心的工作单元,针对这些工作单元建立模型来简化业务。这个模型能帮助我们更深入地理解业务用例,同时,通过这个模型的建立,我们将得到一组“缩小”了粒度的用例。
用例也是不能分解的。正确的说法是抽象。抽象出的概念用例通过包含、泛化、扩展关系连接到基本业务用例。
抽取过程也是概念用例的建立过程。
5.3.1 概念用例模型的主要内容
- 概念用例视图:概念用例用包含、泛化、扩展关系连接到基本业务用例,表示概念用例来源及他们服务于哪个或哪些业务用例
- 概念用例分析
- 分析类视图:抽象出分析类的静态关系。
- 分析常见:绘制对象交互图,从对象的角度去实现概念用例分析场景。
5.3.2 获取概念用例
途径:
- 发现业务用例的相似名称
- 分析客户业务,得知关键业务环节
5.3.3 何时使用概念用例模型
有时候它甚至不需要在正式的文档中出现,也不需要交付给客户,通常也不需要对所有业务用例都提取概念用例。笔者归纳了一些使用和不使用概念用例模型的理由,供读者参考。
使用的理由:
- 业务领域规模庞大,业务用例粒度较大
- 业务网状交叉
- 业务复杂,步骤和分支过多。
- 七个以上泳道存在。
- 早期建立软件架构
:::info 概念模型除了帮助我们简化和理解业务模型,最重要的作用就是帮助我们初步从对象角度来理解业务,从而建立软件架构和产生下一节讲述的系统用例。 :::
5.4 系统用例模型
系统用例模型位于统一过程中先启阶段的末期以及精化阶段的早期。实际上,系统建模就是我们通常所说的需求获取。一般来说,系统二字可以省略,所谓的系统用例就是我们熟悉的用例,系统用例模型也就是我们熟悉的用例模型。所以本节也将省略系统二字,直接使用用例模型这一叫法。
用例是贯穿整个系统开发的一条主线。用例模型即为需求工作流程的结果,可当作分析设计工作流程以及测试工作流程的输入使用。
如果需求分析工作是从业务用例模型开始的,那么到用例模型时应该已经有了足够的信息来源。如果没有业务建模而直接从用例模型开始,那么用例模型将从涉众请求开始,将涉众请求直接转化为用例模型。通常情况下,缺乏业务模型会使得用例模型建立比较困难。
5.4.1 系统用例模型的主要内容
- 业务用例:用例使用精华关系连接业务用例,表明软件过程的可追溯性。
- 概念用例:对用例模型起获取用例的指导作用。
- 用例视图:参与者+用例,是系统功能性需求的高层视图。
- 用例规约:用例执行的相应规则
- 补充规约:与用例相关的非功能性需求。比如响应时间、可靠性、可用性。
- 业务规则:业务规则是客户执行其业务必须遵守的法律法规、惯例、各种规定,也可能是客户的操作规范、约束机制等。
- 用例实现:用例实现一个用例实现是用例的一种实现方式,通常代表不同的应用环境。例如可以通过电话、网站、业务代理完成同一个缴纳电话费用例。
- 用例场景:说明如何交互。
- 分析对象:分析对象是用例场景中代表计算机逻辑的概念化产物。它是将来分析模型的重要来源。
5.4.2 获得系统用例
- 排除用例:
- 参与者不使用计算机来使用这个用例;
- 计算机实现代价巨大,项目成本不可承受。
- 合并用例:
- 用例结果相同或相似
- 抽象用例:
- 结果虽然不同,但是使用过程相同,可以抽象出一个描述行为的用例。
- 补充用例:
- 业务实现无关,但对系统运行必须的非业务需求。比如管理用户账号、备份系统数据。
:::info 本节学习了系统用例模型的基本概念、需要完成的工作以及如何获取系统用例。系统用例模型代表了实际业务转化为计算机功能性需求以后的结果,是系统开发的契约。
我们知道好的需求过程应当从业务建模开始,通过概念模型来分析业务,然后再产生系统用例模型。
但有时候,我们需要描述某些被共同关注的问题,或者与具体参与者无关但对实现业务来说相当关键的问题。这类问题将在下一节通过领域模型来描述。 :::
5.5 领域模型
5.5.1 读者须知
和 领域驱动设计(DDD)的定义 一致,都是通过抽象现实世界当中的事物,以概念化的手段,以模型的方式给予定义。
但定义领域模型的方法和实际用途和 DDD 不同。
DDD 这种方法实质上是先搭建业务架构,再实现具体业务。
本书中的思路恰好相反,是由表及里,由招式而内功的方法。书中所用的领域模型建模方法是用例驱动的模式,先明确业务,通过对业务用例场景、业务对象模型来找出某一个问题的解决方案。
书中的领域模型具体化,范围小,不是全面的业务架构和运行规律。
本书遵循 用例驱动方法(UDD),而不是领域驱动方法(DDD)。
5.5.2 基本概念
领域模型是采用业务对象建立起来的一种模型,我们把领域模型当中使用到的业务对象称为领域类。
领域类的三种典型形式:
- 业务对象实体:表示业务中使用到或产生的东西。如订单、账号。
- 系统要处理的现实世界中的对象和概念。如商品、买家
- 将要发生或已经发生的事件。如购买、付费。
在现实世界中,每一项业务的运行都是由一系列的业务对象实体(包括人物和事物)、事件或概念相互交互而完成的。
5.5.3 领域模型的主要内容
你需要做的是从业务场景出发,针对某些重要的业务问题来建立领域模型,再用业务对象去验证该模型。所以笔者建议先建立业务模型,再来推导领域模型,见图5.6。
建立和验证领域模型可以使用CRC(Class-Responsibility-Collaboration)方法。虽然这个方法没有被包含到UML中,不过在对象分析方面有着独特之处。
:::info 以上几个模型都是针对需求而言的;下一节将开始从计算机的视角来描述业务,正式进入计算机逻辑分析。 :::
5.6 分析模型
在3.7分析类一节中已经介绍过,分析类用于获取系统中主要的“职责簇”。它们代表系统的原型类,是系统必须处理的主要抽象概念的“第一个关口”。
分析模型应当成为面向对象设计的核心
- 分析模型是采用分析类在软件架构和框架的约束下来实现用例场景的产物。
- 分析模型是高层次的系统视图,在语义上,分析类不代表最终的实现。它是计算机系统元素的高层抽象。分析类具化以后才产生真正的实现类。
- 相对而言,设计模型只是分析模型的一种实现手段,分析类具化以后才产生真正的实现类
- 分析模型是MVC模式的经典应用。从分析类的名称就可以看出来。读者应当还记得笔者反复谈到的一个观点:“商业系统无论多复杂,无论什么行业,其本质无非是人、事、物、规则。人是一切的中心,人做事,做事产生物,规则限制人、事、物。人驱动系统,事体现过程,物记录结果,规则则是控制。无论面向对象也好,UML也好,复杂的表面下其实只是一个简单的法则,系统分析员弄明白有什么人,什么人做什么事,什么事产生什么物,中间有什么规则,再把人、事、物之间的关系定义出来,商业建模也就基本完成了。”对比分析类的名称,考虑一下MVC模式,读者应该能够发现分析类在对象世界和现实世界中精妙的对应关系:人、事、物、规则——参与者、边界类、实体类、控制类。
采用分析类来维护系统实现与需求的同步能非常大地节省工作量。因为:
- 设计模型由于要考虑太多的实现细节,如效率、实现语言、框架、程序规范、参数等,要保持设计模型与需求的同步是很困难的
- 从用例场景到设计模型的跨度太大,设计类如何决定更多是凭经验,拍脑袋
- 很多时候根本没必要维护设计模型,例如很多基于数据CRUD(Create, Read, Update,Delete)操作的系统,维护数据处理框架就足够了。至于保持设计与需求的同步,采用分析模型来维持同步就足够了。
5.6.1 如何使用分析模型
笔者推荐先采用时序图,在用例场景中的参与者与系统之间加入一个边界类代表操作界面,在边界类与实体交互之间加入一个控制类代表业务逻辑,然后对照用例场景,一步一步忠实地把用例场景过程用分析类实现出来。例如一个网上购物的业务场景,用分析类绘制的结果如图5.7所示。
定义分析类之间关系要遵循的原则:
- 边界类不应当与实体类之间有依赖关系。边界类只能通过控制类与实体类交互。
- 实体类和实体类之间可以有聚合或组合关系,但不应当有依赖关系。
- 实体类和实体类之间可以有聚合或组合关系,但不应当有依赖关系。它们不应当直接交互,而只能通过控制类间接交互。
- 控制类和控制类之间不应当有聚合或组合关系,如果可能,应当尽量减少依赖关系。
- 正确的依赖关系应当是边界类依赖于控制类,控制类依赖于实体类,而不能反过来。
调整分析类的主要原因和手段主要来自以下几个方面:
- 业务规则
- 业务规则作为分析类交互的一个约束存在,它是需要调整分析类的重要原因
- 结构优化
- 根据面向对象的原则,应当尽量减少对象之间的耦合度。
- 查看备选的分析类,如果分析类之间的关系呈网状,那么就应当考虑调整这个结构。常用手段有加入中介类,让网状结构呈星形结构;或者使用门面模式,将分析类的关联关系集中起来。
- 另一个办法是将对象之间的关系抽象出来,专门用一个关系类来存取对象间的关系。这在UML中被称为关联类。
- 根据面向对象的原则,应当尽量减少对象之间的耦合度。
- 分离职责
- 对象越简单越容易维护。事情太多的分析类考虑分解。
当分析模型完成时,系统主要对象的职责、交互和实现方式已经一清二楚。我们的目的是保持设计与需求同步而不是编写伪代码,对面向对象来说,获得类职责和类方法就足够清楚了,无须展示类的细节。(比如最终用 JSP 还是 ASP 还是 Ajax)
5.6.2 分析模型的主要内容
分析模型架起了现实世界的需求和对象世界的桥梁,架起了软件架构和系统实现之间的桥梁,架起了组件和对象之间的桥梁,也架起了对象和实施之间的桥梁。
5.6.3 分析模型的意义
分析模型采用MVC模式,将用例场景中描述的业务分解为边界(操作界面和展示界面)、控制(业务逻辑)和实体(业务数据),用这三个元素建立实现用例场景的对象模型
分析模型一方面为我们提供了系统如何实现需求的理解,一方面为下一步演化到设计模型提供了极好的输入。
笔者建议在系统用例模型建立之后,设计模型建立之前必须建立分析模型,并一直维护它保持与需求同步。
决定是否需要单独的分析模型时应考虑以下几点:
- 需要设计在多目标环境下使用、带有独立设计构架的系统时,独立的分析模型就非常有用。
- 由于设计的复杂性,因此在向新的团队成员介绍设计时就需要使用简化而抽象的“设计”。
- 在考虑建立分析模型所带来的益处时,必须权衡为确保分析模型与设计模型保持一致性所需的额外工作,因为该模型只代表系统运行方式的最重要的细节。
- 一旦不再对分析模型进行维护,则其价值将迅速衰减。
笔者的做法是维护分析模型与需求同步,加上架构设计、框架设计、编程规范等作为编码实现的约束,而放弃维护设计模型与需求的同步。
5.7 软件架构和框架
软件发展到现在,几乎没有项目再从刀耕火种开始,多少都会采用现成的、开源的或自开发的软件框架,同时,也越来越重视软件架构的建立。
统一过程是以架构为中心的开发模式,如果说用例代表了一个软件项目对需求的定义和理解,那么架构就代表了一个软件项目对系统的定义和理解。
实际上架构和框架是非常不同的。框架是针对某个问题领域的通用解决方案,它通常集成了最佳实践和可复用的基础结构,对开发工作起到减少工作量、指导和规范作用
如果用建设一幢大楼来比喻,架构就是大楼的结构、外观和功能性设计,它需要考虑的问题可以延展到抗震性能、防火性能、防地表下陷性能等;而框架则是建设大楼过程中一些成熟工艺的应用,例如楼体成型、一次浇灌等。
架构是战略性的,它描述部署、职责、战略目标、指挥系统、信息传递等;框架则是战术性的,它描述组织、建设、作战方案、命令下达、战术执行等。
总之,架构是系统蓝图,是对系统高层次的定义和描述。框架是解决方案,是加速和提高系统质量的半成品。
5.7.1 软件架构
对于软件来说,架构需要描述两个方面的内容。这两个方面分别针对业务领域的理解和系统领域的理解,我们可以称之为业务架构和软件架构。
【业务架构】
业务架构的目标是为业务领域建立一个维护和扩展的逻辑结构,描述业务的构成。
业务架构是软件架构的重要输入。
业务架构来源于两个主要的输入:业务用例和领域模型。
业务架构可以使用领域包和组织结构包来表示业务主要领域和组织结构关系。
业务架构描述了业务领域主要的业务模块及其组织结构,从某个角度说,业务架构图很像商业模式。
(目的)
- 建立业务架构的目的除了理解业务之外,最重要的一个作用是为业务重组做准备。
业务架构需要一份文档:
- 描述领域包职责,与其他包之间的关系。
- 引用用例模型阐述典型业务在这个业务架构中运行方式。
业务架构与核心模型的关系可用图5.11来表示。用例模型、领域模型所描述的业务过程,通过抽象可得到业务架构。
笔者在这里采用UML元素来绘制业务架构图仅仅是因为本书要讲述UML。在实际工作中,笔者更愿意使用其他绘图工具来绘制,例如Visio,然后再用业务架构文档将它们组织起来。
【软件架构】
软件架构需要在业务架构的基础上引入计算机环境,计算机环境包括硬件环境和软件环境。
- 硬件环境指网络拓扑结构、服务器及其他设备等,而软件环境则指操作系统、应用服务器、中间件、数据库以及其他第三方支持软件等。
- 软件架构需要说明业务架构如何分布在计算机环境中,并得以执行。
典型的软件架构的两个视角:
- 广度视角:常见的软件层次结构,关注分层,规定每一层的职责以及层之间的通信标准。使用层包元素绘制。
- 深度视角:是指广度视角中每一层的详细说明,它关注每一层以及每个部分的具体实现架构
广度视角和深度视角将软件架构立体化了,图5.14展示了这种立体化的结构。层次构成了广度视角维度,而每一个层次里的包、类的结构构成了深度视角维度。
【架构描述】
架构文档需要描述到以下方面:
- 业务架构概述:业务概要包括背景、商业模式、商业目标、系统目标
- 组织结构:客户方的组织结构,各部门职责和关系,各部门在业务架构中的作用
- 业务模块:每个业务模块在整个业务中要完成的商业目标,与相关业务模块的关系,主要业务流程。
- 业务对象模型:用例模型中获得的主要业务对象模型、领域模型。
- 典型用例场景:用例模型中挑选典型用例场景,描述该场景如何串联各个业务模块,以及各个业务模块主要处理事项以及产生的结果。
- 软件架构概要:系统的设计目标和设计原则,以及软件架构要描述的内容。
- 计算环境:系统运行的硬件和软件环境。
- 软件层次:展示软件层次结构,描述每一层次职责、设计目标和约束(包括标准、规范和使用的框架),并描述每一层次之间交互所使用的通信协议和接口。
- 实现架构:设计视图描述模块的实现架构,即时序图或交互图。
- 协议和接口:各层之间的通信协议和接口进行详细描述。
- 软件框架:描述各框架在整个架构中的位置及职责,和其他部分的交互。
- 典型用例场景的架构实现:动态视图实现典型用例场景。
- 非功能性需求:可靠性、可用性、可扩展性、可移植性。容错能力,友好性,响应时间。
5.7.2 软件框架
软件框架是针对一个普遍问题的最佳实践或解决方案,它通常都是一个半成品,提供基本类库、编程模型和编程规范,甚至包括IDE工具。
那么软件框架与类库之间又有什么差别呢?类库是编程工具,帮助编程人员简化工作,提高工作效率。例如编程时要处理文件,java.io.*下就有许多现成的类来提供文件处理的函数。
类库只负责提供大量的现成工具,但它不管编程者会怎么使用它,类库自己是不能运行的。而软件框架除集成了必要的类库之外,最重要的是提供了一个编程模型,并在此编程模型之上完成了许多实际的功能,是一个半成品。它除了帮助编程者快速开发,还规定了编程者必须怎样编程的规范。
绝大部分问题都能找到成熟的框架,完全没有必要自己费时费力开发。例如Web开发就有Struts、WebWork、JSF、AJAX等;OR-Mapping则有Hibernate、RBatis、EntityBean、MacrobjectNObject等,甚至全文搜索、报表生成、数据采集、事务处理、异常处理、日志处理都有可用的框架,这个名单可以一直列下去。
另一方面,成熟框架都有着明确和严格的接口定义、规范和编程模型,非常有助于约束开发人员开发出风格统一的代码。
5.7.3 何时使用架构和框架
如果是一个规模较小的项目,例如几个人几个月的小项目,维护一个架构就不值得。
架构选择的两种策略:
- 开发自己的架构。
- 但开发自己的架构不是说什么都自己从头来过,而是选择一些成熟的软件框架,自己定义如何组织它们来开发一个架构。
- 选择成熟的架构。
- 这一般是发生在项目规模比较大,客户投入资金购买了企业级应用服务器产品的情况下,例如IBM的Websphere系列产品。
为什么笔者说框架是必需的呢?这是从提高代码质量的角度来说的。提高代码质量的几个重要因素是优良的设计、稳定的核心、尽可能的复用、严格的编程规范和统一的代码风格。
5.8 设计模型
:::info 设计模型是一个描述用例实现的对象模型,它可作为对实施模型及其源代码的抽象。设计模型用作实施和测试活动的基本输入。 :::
以上是官方定义。
通俗地说,设计模型就是我们所熟知的详细设计。设计模型采用设计类绘制,它需要考虑实现语言、架构、框架、编程模型、规范,目标是用程序逻辑来实现用例。
设计模型是编码实现之前的最后一道建模工序,如果投入相当的人力物力,设计模型可以做到伪代码级别,通过工具可以直接生成可执行代码。
不过从作者自己的实践经验上来看,要将设计模型做到伪代码的程度并维护与实现代码的统一,其代价是很高昂的。
5.8.1 设计模型的应用场合
设计模型与编码距离最近,可以指导编码。但很难使用设计模型保持与需求的同步,但在软件架构、框架和典型场景下有着很大作用。
- 软件架构场合。
- 软件架构师高层次的系统视图。软件架构师需要用设计模型来解释软件架构如何运行,以及描述应当如何使用架构的编程模型,让开发人员知道架构如何运行,编码时应当怎样使用架构。
- 软件框架场合。
- 软件框架是个半成品,包括一系列的类库和编程模型。系统设计师应当建立一个设计模型来解释框架如何运行,如何使用框架的类库,以及开发时应当怎样遵循编程模型。
- 典型场景场合。
- 众多场景抽取一个来建立能够完成增删改查的设计模型。
- 日志处理、事务管理、异常处理、消息机制。
5.8.2 设计模型的主要内容
与分析模型类似,设计模型也是用对象来实现用例的。不同的是,分析模型采用分析类而设计模型采用设计类。
分析类抽象层次高于实现方式和实现语言,所以不需要处理过多的细节;
而设计类与实现方式和实现语言有关,例如如果决定使用Java作为编程语言,在使用设计类实现用例的时候就需要考虑到Java的语言特性。
我们可以想象一下一个实际的项目有多少个类,如果将这些设计类都绘制出来,工作量可以想见。但是如果不绘制,我们又不能表达代码逻辑与需求的一致。而即使花了大力气终于把这项艰难的任务完成了,一点需求变动就会给我们带来大麻烦。
因此,笔者的做法是维护设计与需求一致的工作交给分析模型,设计模型仅仅针对上一节所描述的场合建立和维护,并且保持这些场合中的设计类向分析类的映射,如图5.17所示。这样的用法中,设计模型所针对的场合都是普遍的问题,相对都是稳定的、不变的。同时,分析模型透明于实现,也是比较稳定的,因此维护这个映射关系并不困难。
5.8.3 从分析模型映射到设计模型
如何从用例到设计?统一过程的官方文档提供了一组规则和指导原则来帮助从分析类映射到设计类。
- 分析类代表设计元素的实例所承担的角色;
- 这些角色可以由一个或多个设计模型元素来实现。
- 此外,单个设计元素可以实现多个角色。
实现分析角色的可能方法:
- 一个分析类是设计模型中的单个类;
- 一个分析类是设计模型中某个类的一部分;
- 一个分析类是设计模型中的聚合关系类;
- 一个分析类是设计模型中同一个类的继承;
- 一个分析类是设计模型中一组功能相关的类;
- 一个分析类是设计模型中的一个包(意味着成为一个构件)
- 一个分析类是设计模型中的一项关系
- 分析类之间的一项关系成为设计模型中的一个类。
- 分析类处理功能性需求以及来自问题领域的模型对象,设计类处理费功能性需求以及来自解决方案领域的模型对象。
- 分析类代表:我们希望支持的对象。比如分析类一部分可通过硬件来实现,那么就不用在设计模型中建模。
其他建议:
- 设计类要考虑实现语言、实现方式和运行环境。
- 设计类要考虑架构的约束。比如接口,分层,协议。
- 设计类必须考虑框架的约束。需要了解框架类库的使用办法和编程模型。
- 绘制设计类交互图复杂的场景,可以考虑将带有普遍性质的交互场景建立为典型应用行和。
5.9 组件模型
组件总是用来容纳分析类或设计类的。
从这个角度说,可以把组件理解为一种特殊的“包”,只不过普通的包起到组织和容纳的作用,而组件的组织行为却有着特别的目标:这些分析类或设计类被组织起来完成一组特定的功能。
组件四特性:
- 完备性
- 独立性
- 逻辑性
- 透明性
如何组织代码保证四特性?架构。现有软件架构,才能建立组件模型。
对组件来说,架构是组件的设计规范,是组件的安装平台,是组件的运行环境,也是组件的管理环境。
生产组件的目的是为了复用,也就是说一个真正意义上的组件可以在多个系统中直接使用而不需要更改。
典型的架构与组件的例子有:J2EE架构与EJB组件、.NET架构与COM组件、SOA架构与SCA组件等。
另一方面,组件与部署也是息息相关的。架构决定了计算机的软硬件环境,组件将被部署到架构所决定的软硬件环境中去。
一开始组件并没有实现代码,它只有预定义的功能和对外暴露的接口。例如对一个论坛来说,我们可以定义这样一些组件来实现论坛的功能,如图5.18所示。
定义组件的目的:
- 成为可复用的单位;
- 每个组件承担特定的功能;
- 每个组件成为可独立部署的单位;
- 每个组件都遵循架构规范。
借此我们可以看到,组件并不是从分析模型或设计模型中推导出来的,相反,是因为我们基于复用、独立部署、构件化、商业用途等原因先定义组件,再到分析模型或设计模型中来寻找对应的实现的。
我们可以看到,组件实现可能使用到多个包中的多个类。编译组件时,组件包就拥有了这些类的一个拷贝。
另外,组件也很可能和非组件程序同时存在,一个典型的例子是企业内部系统并不使用组件,但为了向企业外部提供一些服务而定义一些组件,让企业外部的用户通过这些组件所描述和暴露出来的功能来使用该系统的某一部分功能。(授权登录?)
5.9.1 何时使用组件模型
以下场景可以决定不使用组件图,反之应该建立:
- 项目非分布式系统;
- 不需要向第三方提供服务;
- 项目不涉及将某部分业务功能单独抽取出来形成一个可复用的单元;
- 项目不涉及和客户现有系统或第三方系统集成的要求;
- 未采用架构开发,缺乏部署环境和运行环境。
5.9.2 广义组件的用法
广义组件—>物理代码组织的包—>构件。
- 模块:描述应用系统中的各个逻辑模块之间的关系,如登录模块;
- 子系统:描述系统中各个子系统之间的关系。如发布话题子系统;
- 类库:描述应用程序中使用到的或生成的各个公共或基础类库之间的关系。如 dll、jar。
- 可执行程序:描述应用系统中各个可执行部分之间的关系。如 exe、ear。
- 包:描述应用系统中各个程序包之间的关系。如 web 包、Javabean 包等。
:::info 作者认为狭义的理解更有意义一些。失去了完备性、独立性、逻辑性和透明性的广义的组件价值也大打折扣。 :::
5.10 实施模型
实施模型由配置节点和组件组成。
【配置节点】:使用节点元素绘制,描述系统硬件的物理拓扑结构。
【组件】:使用组件元素绘制,表示在配置图中描述的结构上执行的软件。
实施模型中一个节点表示一个计算单元,通常是某种硬件,例如一台主机或工作站。
组件则代表可执行的物理代码模块,例如一个可执行程序。
因此,配置图显示运行时各个组件在节点中的分布情况。
【何时使用实施模型】
- 分布式系统描述各种资源在不同节点上的分布情况。
- 从事的系统需要与来自多方的程序、模块等交互,要描述这些程序的分布情况。
- 系统由多个硬件设备,如 POS 机相关的应用系统。
反之,系统是一个集中式的,代码集中部署在一台主机上,没必要使用。
:::info 小结:核心视图是使用核心元素来表达某个观点,核心模型是使用多个视图来完成某个阶段的工作。
上述的核心模型不一定在一个软件生命周期里必须全部采用。
模型决定视图,视图决定元素,软件过程决定模型。
统一过程是将 UML 的核心模型使用得最为全面的。
:::
6. 统一过程核心工作流简介
怎样才能明确知道自己想要做什么呢?
- 软件过程。
软件过程明确了软件的生命周期,明确了软件生命周期过程中的成果物和可交付物,同时也就明确了需要什么样的模型。换言之,是软件过程明确了在软件项目的哪个阶段使用哪些模型。
对软件项目来说,OO也好,面向过程也好,UML也好,UC矩阵也好,这些都不是最重要的,软件项目真正的灵魂是软件工程
虽然任何一种软件过程都可以使用UML,毋须讳言,统一过程仍然是对UML使用最为精深的,毕竟UML和统一过程师出同源,本书也将结合统一过程来讲述以后的章节。
将列举出使用UML最多,也最为常用的几个工作流程
- 业务建模工作流程
- 系统建模工作流程
- 分析设计工作流程
- 实施建模工作流程
6.1 业务建模工作流程。
先启阶段,会用到的模型:业务用例模型,概念用例模型和领域模型。
6.1.1 工作流程
- 业务领域很成熟的业务,只需执行第一条路径
- 业务领域客户有改进流程的打算,执行第二条路径。
- 业务领域客户有自动化改革业务模式的打算,要执行第三条路径。
- 针对不太清楚的领域建模。
概念模型是建立业务架构的主要输入,因此,假设有意在项目初期就建立业务架构并开发原型系统,那么应当执行概念模型建立的路径。
6.1.2 活动集和工件集
统一过程定义了业务建模工作流程中的主要角色以及他们应当执行的活动,其定义如图6.2所示。
业务流程分析员对应的工件是可交付物:
- 业务词汇表
- 业务规则
- 业务用例模型
- 业务对象模型
- 目标组织评估
- 业务前景
- 业务架构文档
- 补充业务用例规约
业务设计员对应的工件都是实施工件:
- 业务用例
- 业务主角
- 业务用例实现
- 组织单元
- 业务实体
- 业务角色
6.1.3 业务建模的目标和背景
业务建模的目的:
- 了解目标组织(部署系统的组织)的结构及机制。
- 了解目标组织存在的问题并确定改进的可能性。
- 确保客户、最终用户和开发人员就目标组织达成共识。
- 导出支持目标组织所需的系统需求。
业务建模的作用:
- 作为需求工作流程的输入来了解对系统的需求。
- 作为分析设计工作流程的输入,来确定设计模型中的实体类。
6.1.3.1 场景#1——组织图
您可能需要构建组织及其流程的简图,以便更好地了解对正在构建的应用程序的需求。
6.1.3.2 场景#2——领域建模
如果您构建应用程序时的主要目的是管理和提供信息(例如,订单管理系统或银行系统),那么您可能选择在业务级别上构建该信息的模型,而不考虑该业务的工作流程。这就称为领域建模。
6.1.3.3 场景#3——单业务多系统
如果您正在构建一个大的系统(即一系列的应用程序),那么一个业务建模工作可能成为数个软件工程项目的输入。
在这种情况下,通常将业务建模工作本身当做一个项目。
6.1.3.4 场景#4——通用业务模型
场景:供多个组织使用的应用程序(例如:销售支持应用恒旭或结账应用程序)。
做法:业务建模,按组织的经营方式对应用进行调整,避免复杂需求。
6.1.3.5 场景#5——新业务
场景:启动全新业务通过构建信息系统来支持。
做法:业务建模,找出对系统的需求,确定新业务是否可行。业务建模当做一个项目。
6.1.3.6 场景#6——修改
业务重建需要业务建模。
分数个阶段:
新业务展望、对现有业务实施逆向工程、对新业务实施正向工程以及启动新业务。
:::info 应当理解,业务模型与计算机无关,无论有没有计算机,无论是否建立IT系统,这些业务都客观存在,哪怕是手工的。 :::
工作流程的目的是得到业务架构,在业主和软件开发商之间建立 IT 建设范围和目标的共识。同时,这些模型又作为系统建模工作流程的输入。
6.2 系统建模工作流程
系统建模即通常意义上的需求过程,主要使用系统用例来建立。
6.2.1 工作流程
统一过程中定义系统建模的工作流程如图。
图中展示系统建模的关键活动。活动和执行顺序可选,根据实际情况来定。
系统建模工作位于先启阶段和精化阶段
- 先启阶段侧重:分析问题和理解涉众需要。
- 精化阶段侧重定义系统和改进系统定义。
- 管理系统规模和管理需求变更的活动贯穿项目始终。
系统建模的首要问题是要了解我们利用该系统试图解决的问题的定义和范围。
6.2.1.1 分析问题
- 实际问题是什么
- 涉众有哪些;
- 业务角度界定解决方案;
- 制约解决方案的因素
6.2.1.2 理解涉众需求
讨论在业务模型这一级上展开。
获取需求活动的技巧:
- 访谈
- 集体讨论
- 概念原型设计
- 问卷调查
- 竞争性分析。
结果表达:
- 图文并茂的请求或需求列表
- 相互之间优先级列出。
6.2.1.3 定义系统
定义系统指的是解释涉众需求,并整理成为对要构建系统的意义的明确的说明。
系统定义初期要确定的内容:
- 需求构成
- 文档格式
- 语言形式
- 需求的具体程度(需求量及详细程度)
- 需求的优先级和预计工作量
- 技术和管理风险
- 最初
6.2.1.4 改进系统定义
用例方法是传达系统目的和定义系统细节的一种行之有效的方法,它常与简单的可视化原型结合使用。
用例有助于为需求提供一个环境,利用它可生动地说明系统使用的方式。
6.2.1.5 管理系统规模
为确保尽早解决或降低项目中的风险,应以递增的方式开发系统。要慎重选择需求,以确保每次增加都能缓解项目中的已知风险。
除了控制开发过程本身,您还需控制需求的来源,并控制项目可交付工件的外观。
6.2.1.6 管理需求变更
可变因素常有,设计弹性的结构,使他能适应变更。
管理变更包括建立基线、确定需要追踪的重要依赖关系、建立相关项之间的可追踪性,以及变更控制等活动。
6.2.2 活动集和工件集
系统建模活动集如下:
在统一过程中,完整的系统建模工作完成后,应当得到如图6.6所示的工件集。
6.2.2.1 前景
适用于描述产品型项目。描述软件的商业目标等信息说明产品价值,商业回报。
6.2.2.2 涉众请求
凡是与项目有关系的人或组织或系统,都是涉众。
涉众请求可以来自以下途径:
- 涉众访谈的结果。
- 获取需求讨论会和研讨班的结果。
- 变更请求。
- 工作说明。
- 方案征求。
- 任务说明。
- 问题说明。
- 业务规则。
- 法律法规。
- 遗留系统。
- 业务模型。
6.2.2.3 需求属性
需求属性是管理工具,用来管理和追踪每个需求在项目进行过程中的变化情况:优先级变化,进展情况。
6.2.2.4 软件需求规约
即需求规格说明书。它需要将用例模型(包括用例规约、用例视图等)和补充规约、系统界面原型等集中起来,作为一份完整的需求规格说明书。
6.2.2.5 用例示意板
Story Board
用例是被界面来使用的。而用例示意板就是用来描述界面如何使用用例的这一信息。
6.2.3 系统建模的目标
系统建模工作流程的目的是:
- 与客户和涉众在系统的工作内容方面达成一致。
- 使开发人员能了解系统需求。
- 定义系统边界。
- 为计划迭代的技术内容提供基础。
- 定义系统的用户界面,重点是用户的需要和目标。
系统模型是业务模型到计算机系统的映射。
6.3 分析设计建模工作流程
分析设计建模 = 概要设计+详细设计。
使用工具:分析模型+设计模型。
使用时机:精化阶段。
6.3.1 工作流程
在现实中,很多项目的设计只有两部分:界面设计和数据库设计。(适合许多较小的项目)。
:::info 抽象层次决定系统的复杂度。 :::
6.3.1.1 定义和改进架构
统一过程定义了复杂的如图所示的定义架构过程和改进架构过程。
大量中小型项目不会执行如此多的活动,按需取用。
6.3.1.2 分析行为
即分析用例场景。
使用分析类或者设计类,结合架构实现用例场景。
输出:对架构就要重要意义的分析类和设计类。
工作如图:
采用分析类或设计类实现用例时,不要过于关注细节,因为会导致工作量的激增,同时过量的信息会降低人脑处理的能力。
:::info 因此要提高抽象层次而屏蔽掉许多细节。 :::
6.3.1.3 设计组件(构件)
Component
在统一过程中,组件是实施单元,它们被安放在架构的某一个位置然后由架构驱动执行。
大部分项目中可复用的部分总是系统范围的,例如日志处理、事务管理、异常处理等。
组件的定义是一个可复用的单元,这个定义也是有点嚼头的。广义上来说,凡是可以部署到一个框架里面的东西都可以称为组件,JSP、ASP、JavaBean、COM……
在统一过程中区分了实时组件和非实时组件,它们分别对应着实时系统和非实时系统。
- 实时系统:系统对响应时间和可靠性有着严格的要求。如许多工业控制软件,如果有延时容易出现严重后果。
- 非实时系统:系统对响应时间和可靠性相对容忍。如大多数商业信息管理系统,报表统计。
6.3.1.4 设计数据库
在面向对象的设计中,关系型数据库仅是用来持久化数据的一种方式,并非设计的中心,重要的是对象设计。
然而大多数设计围绕数据库进行,对象反而退居其次。所谓的对象就是对应一张物理数据表的一行数据的方式。
这种设计方式严格说不是面向对象的方法,而是面向对象的方法——尽管使用了面向对象的语言或者工具(例如 OR-Mapping 工具)。
适合简单的以处理数据为目的的信息系统。但要产品化或者想成为行业标准软件,应提升抽象层次,重构代码,形成高内聚低耦合的模块,而关系型数据库做不到这一点。
:::info 如果代码与数据库结构绑定得非常紧密,那么一定因为数据库的不可抽象性而会失去面向对象的所有优势。 :::
6.3.2 活动集和工件集
统一过程定义的分析设计工作流程会产生活动集和工件集。
6.3.3 分析设计的目标
分析设计的目的在于:
- 将需求转换为未来系统的设计
- 逐步开发强壮的系统构架。
- 使设计适合于实施环境,为提高性能而进行设计。
分析设计与其他工作流程的关系为:
6.3.4 推荐的分析设计工作流程简介
统一过程对中小型项目过重。
这里说下一个简化的分析和设计过程。
分析过程的常用项:
- 获取分析类,用分析类实现用例,保证分析类与需求的同步。
- 建立分层模型,然后将分析类映射到软件的分层模型中。
- 确定是否有必要建立组件模型。
- 确定是否有必要建立实施模型。
设计过程的常用项:
- 确定实现语言,制定编程规范。
- 如果使用了架构或框架,提供编程模型实例,作为编程指南和约束。
- 每一个软件层次进行必要的设计。
- 一方面设计该层上所有类必须实现的接口、遵循标准。
- 另一方面设计处理系统中的公共事务,例如:日志、事务、异常等基础模块。
- 典型用例场景实现。
6.4 实施建模工作流程
实施建模的目的,是建立组件及其所在的实施子系统的集合。
6.4.1 工作流程
在一个以架构为导向、以迭代为生命周期的项目里,建立实施模型是很有意义的。
6.4.2 活动集和工件集
6.4.3 推荐的实施建模工作流程
常用的实施建模工作流程:按用例的优先级,采用多个迭代的方式来实施项目。
- 需求分析员确定用例优先级
- 设计师确定模块和代码包
- 编码人员编写代码(可按核心模块、界面、编码、数据库等职责分组)
- 测试人员编写测试用例并测试系统
- 集成员负责管理这些代码并编译和集成它们。
实施模型的思想,以用例为基础来分工,因为一个用例就是一个可独立执行的单元,所以每次迭代的目标可定义为实现哪些用例。
赶工的结果通常是以降低质量为代价的。
统一过程试图用最稳定、最全面、最安全的方法囊括软件开发的全部,看上去的确是很笨重的,中小型项目也的确玩不起。但是,统一过程更重要的是揭示了软件生产的秘密,运用这些思想,完全可以定制出适合自己项目的简化过程。哪怕不打算自己裁减,也可以将统一过程中有用的思想与极限编程或敏捷方法等这些轻量级的方法结合起来。
如果问统一过程最大的价值是什么,那么我会说是迭代式软件生命周期和用例驱动。
7. 迭代式软件生命周期
迭代计划于里程碑计划不同。真正迭代的意思是,每一个迭代都经历一次完整的软件生命周期。即每次迭代都有需求、分析、设计、实施。也就是说,每一次迭代的结果都能得到一个可运行的系统。
对于软件来说,最主要的一个风险就是需求变更或者需求理解错误。一个最有效的应对办法是尽早验证,并控制风险的影响规模。
- 尽早给客户提供一个可运行的系统。
- 迭代式开发的目的就是做到这一点:当需求还在进行中,需求人员能从众多需求中发现关键需求,在早期迭代中包含。需求人员在细化其他需求的同时,关键需求的设计和开发已经开始。
常见风险:
- 需求风险
- 技术风险
- 人力资源风险
瀑布模型,在开发后期不得不安排更多的开发人员完成工作量。如果更早开始,由于开发周期拉长,因此可以投入更少的开发人员。
UML 建模,用例就是最好的迭代点。