什么是UML
对一个软件系统而言,UML语言具有以下的重要功能:可视化(Visualizing)功能、说明(Specifying)功能、建造(Constructing)功能和建文档(Documenting)功能。
可视化功能
可视化可以促进对问题的理解和解决,并且方便熟悉UML的设计师彼此交流和沟通。
可以较容易地发现设计草图中可能的逻辑错误,保证最后完成的软件确实能按照要求运行,避免和减少意外发生。
说明功能
对一个系统的说明应当通过一种通用的、精确的、没有歧义的通信机制进行,显然UML的特性使得UML很适合于这种说明工作。
系统的整体设计可以指导软件的开发过程。由于重要的决定均可以在开始写代码之前就做出,因此可以减少低质量的代码,进一步降低开发成本。
建造功能
UML有它自己的语法规则,这使得人们可以使用建模工具软件对一个系统设计模型加以解释,并将设计模型映射到一种计算机语言(如Java)上。这也就是说,使用一种建模工具可以大大加快建模和系统设计的过程。
通过UML可以看到总体的图像,这样一来,可以均衡调配系统所消耗的计算机的资源,使系统更有效率。因为系统的设计首先完成,所以很容易就能发现可以复用的代码。代码能够高效率地实现复用,可以降低开发成本。
建文档功能
使用UML进行设计可以同时产生系统设计文档。
由于使用UML设计的软件系统在写出代码之前就有了专业化的设计和文档资料,所以程序员事先精确地知道他们的计划是什么。当需要修改一个已有的系统时,如果能找到那个系统的UML文档资料,则会节省学习时间,使修改工作事半功倍。这样可以降低维护成本。
如果在项目进行过程当中,有新的程序员参加项目的话,这些程序员可以借助UML图形文档资料很快熟悉开发中的系统。
UML包括什么
UML包括以下的图:
- 用例图(Use case diagrams)
- 类图(Class diagrams)
- 时序图(Sequence diagrams)
- 合作图(Collaboration diagrams)
- 状态图(Statechart diagrams)
- 活动图(Activity diagrams)
- 构件图(Component diagrams)
- 部署图(Deployment diagrams)
在所有的这些图里面,用 例图、类图和 时 序图是最为有用的。
根据这些图的用意,可以将它们大体上划分为结构型图和行为型图两种。
- 结构型图描述了系统的静态结构,在显示一个系统已有的类及它们之间的静态关系时最为有用。
- 行为型图描述一个系统的动态性质,在显示系统的元素如何协作产生满足要求的系统行为方面最为有用。
结构型图
| 图的名字 | 介绍 |
|---|---|
| 类图(Class Diagram) | 类图描述一些类、包的静态结构和它们之间的静态关系 |
| 对象图(Object Diagram) | 对象图给出一个系统中的对象的快照 |
| 构件图(Component Diagram) | 描述可以部署的软件构建(比如 jar 文件)之间的静态关系 |
| 部署图(Deployment Diagram) | 描述一个系统的拓扑结构 |
显然,要描述一个设计模式的静态结构,使用类图和对象图就很合适。
行为型图
| 图的名字 | 介绍 |
|---|---|
| 用例图(Use Case Diagram) | 用例图描述一系列的角色和用例之间的关系。可以用来对一个系统的最基本的行为进行建模 |
| 活动图(Activity Diagram) | 描述不同过程之间的动态接触。活动图是用例图所描述的行为的具体化 |
| 状态图(State Diagram) | 描述一系列对象的内部状态及状态的变化和转移。注意一个类不能有两个不同的状态图 |
| 时序图(Sequence Diagram) | 时序图是一种相互作用图,描述不同对象之间信息传递的时序 |
| 协作图(Collaboration Diagram) | 协作图是一种相互作用图,描述发出信息、接收信息的一系列对象的组织结构 |
显然,要描述一个设计模式的行为特性,使用状态图和时序图就很合适。
只要有意义,所有类型的 UML 图都是可以混合在一起使用的。比如,一个对象图可以与一个类图同时出现在一个结构图中,一个构件图中可以有类图出现等(但一些 UML 建模工具软件不一定允许这样做)。
应当指出的是,一个使用 UML 的系统设计,往往是从使用用例图开始的,而且一个设计应当是以使用用例驱动的。
类图
类图是显示出类、接口以及它们之间的静态结构和关系的图。类图最基本的元素是类或接口。
描述类的类图
表示类的框分成以下几层: ● 类名
● 属性清单
● 方法清单
● 性质清单
如果一个类有内部成员类,它的类图就会有五层。在类的类图中,除了类名层是不能省略、必须显示的以外,其他几层都是可以在UML图中省略的。
第一层是类名。类名如果是正体字,表明类是具体的,即可以实例化的类,变量名如是斜体字,表明类是抽象的。显然在图中给出了一个具体的类。类名是不能省略、必须显示的。
第二层是属性层。一个属性可以是 public、private 或 protected。一个属性的左面如果有个加号(+),表示它是 public;左面如果有一个减号(-),表示它是 private;左面如果有一个井号(#),表示它是 protected。
第三层是方法层。一个方法的左面如果有一个加号(+),表示它是 public;左面如果有一个减号(-),表示它是 private;左面如果有一个井号(#),表示它是 protected。如果方法的下面有一道下划线,表明这是一个静态的方法。一个方法有几个要素:方法的名字、方法的变量名与变量的数据类型,以及方法的数据类型。一个类的构造子也属于方法,但是构造子是特殊的、没有返还类型的方法。一个类的构造子不一定是 public,它也可以是 private 或 protected,例如单例模式。
第四层是性质层。性质是由一个属性即由一个内部变量,一个赋值函数(mutator)和一个取值函数(accessor)组成的结构。
// 取值函数
public String getJobTitle() {
return jobTitle;
}
// 赋值函数
public void setJobTitle(String jobTitle) {
this.jobTitle = jobTitle;
}
// 内部变量
private String jobTitle;
描述接口的类图
接口的类图与类的类图几乎一样,惟一的区别是接口的名有“interface”的字样。一个接口不会有性质,可以有方法的声明、public 和 final 静态内部成员类。设计师可以选择只显示一个接口的各方法项示各属性项而不显示方法项等。
类图中的关系
在类与类之间,会有连线指明它们之间的关系。类和类、类和接口、接口和接口之间可以建立以下几种关系:一般化关系、关联关系、聚合关系、合成关系和依赖关系,这几种关系都是静态的。
一般化关系
一般化(Generalization)关系表示类与类之间的继承关系,接口与接口之间的继承关系,或类对接口的实现关系。一般化的关系是从子类指向父类的,或从实现接口的类指向被实现的接口,与继承或实现的方向相反,如下图所示。
在 UML 类图中,继承关系用带空心三角箭头的实线来表示,箭头从子类指向父类。
在 UML 类图中,实现关系使用带空心三角箭头的虚线来表示,箭头从实现类指向接口。
关联关系
关联(Association)关系是类与类之间的联接,它使一个类知道另一个类的属性和方法。关联可以是双向的,也可以是单向的。双向的关联可以有两个箭头或者没有箭头。单向的关联使用带箭头的实线,表示关联的方向,如下图所示。单向的关联更为普遍,通常不鼓励使用双向的关联。
在 Java 语言里,关联关系是使用实例变量实现的。比如在上面的 Driver 类中,就出现了一个类型为 Car 的实例变量,这个变量实现了这两个类之间的关联关系。每一个关联都有一个名字,在上面的例子里,关联的名字是 Drives。
在每一个关联的端点,还可以有一个基数(Multiplicity),表明这一端的类可以有几个实例。常见的基数有下表所示:
| 基数 | 含义 |
|---|---|
| 0..1 | 零个或者一个实例 |
| 0..* 或者 * | 对实例的数目没有限制(可以是0) |
| 1 | 只有一个实例 |
| 1..* | 至少有一个实例 |
其中记号 n..m 表明一个取值区间,也就是 n ~ m 个实例。
一个关联关系往往可以进一步确定为聚合关系或者合成关系。
聚合关系
聚合(Aggregation)关系是关联关系的一种,是强的关联关系。聚合是整体和个体之间的关系。与关联关系一样,聚合关系也是通过实例变量实现的。但是,关联关系所涉及的两个类是处在同一层次上的,而在聚合关系中,两个类是处在不平等的层次上的,一个代表整体,另一个代表部分。
在 UML 类图中,聚合关系可以用带空心菱形的实线来表示,菱形指向整体。下图所示是大学和教师的关系图:
关联与聚合仅仅从 Java 语法上是分辨不出的,需要考察所涉及的类之间的逻辑关系。如果不是很确定一个关系是不是聚合关系,可以将之设置为关联关系。
合成关系
合成(Composition)关系是关联关系的一种,是比聚合关系强的关系。它要求普通的聚合关系中代表整体的对象负责代表部分的对象的生命周期,合成关系是不能共享的。代表整体的对象需要负责保持部分对象的存活,在一些情况下负责将代表部分的对象消灭掉。代表整体的对象可以将代表部分的对象传递给另一个对象,由后者负责此对象的生命周期。换言之,代表部分的对象在每一个时刻只能与一个对象发生合成关系,由后者排他地负责其生命周期。
在 UML 类图中,组合关系用带实心菱形的实线来表示,菱形指向整体。下图所示是头和嘴的关系图:
依赖关系
依赖(Dependency)也是类与类之间的连接,依赖总是单向的。依赖关系表示一个类依赖于另一个类的定义。
在 UML 类图中,依赖关系使用带箭头的虚线来表示,箭头从使用类指向被依赖的类。下图所示是司机和汽车的关系图,司机驾驶汽车:
一般而言,依赖关系在 Java 语言中体现为局域变量、方法的参量,以及对静态方法的调用。换言之,一个类 A 的某一个局域变量的类型是另一个类 B,那么类 A 就依赖于类 B。如果一个方法的参量是另一个类 B 的实例,那么这个方法所在的类 A 依赖于类 B。如果一个类 A 调用另一个类 B 的静态方法,那么类 A 依赖于类 B。
如果类 B 出现在类 A 的实例变量中,那么类 A 与类 B 的关系就超越了依赖关系,而变成了某一种关联关系。
一般而言,每一个类图都应当有类、关联关系、基数。
时序图
作为交互图的一种,序列交互图按照时间顺序从上往下显示每个使用案例。
在一个时序图中,垂直的虚线叫做生命线,它代表一个对象存在的时间。每一个箭头都是一个调用,这个箭头从调用者对象连接到接收者对象的生命线上的激活条上。每一个激活条代表调用所持续的时间。
状态图
定义一个具有有限个内部状态的机器,因此状态图又称为有限状态机,对象被外界的事件激发,从而从一个状态转换到另一个状态。
黑点表示起始状态,方框表示具体状态。从起始状态到具体状态的、有箭头的连线表示状态的过渡。过渡连线的标签通常分为两部分,由一个斜线分开,斜线的第一部分是引起状态过渡的事件,第二部分是事件发生所引起的操作,本例中,只有第一部分,没有第二部分,因为瓶子没有动作。
反身过渡连线,表示事件发生时,对象会过渡回当前状态。本例中,“未满”状态有反身过渡连线,表示瓶子在被加灌汽水是一个持续不断的过程。
方括号中的事件是发生的保护条件,本例中,“已加灌量 < 容量”,它是保证过渡关系发生的条件。
状态可以嵌套,一个状态中可以有别的状态。大的状态叫做父状态或者超状态,小的状态叫做子状态。