UML软件工程组织

图论思想与UML应用(上)
作者: 温昱
摘要:

UML是什么?是建模语言。本文就从语言和思维的关系谈起,说明UML对思维具有反作用——是促进思维还是阻碍思维,全凭UML的使用者对UML内涵的掌握程度了。那么,如何达到“UML促进思维”的境界呢?本文结合实例,说明图论思想在UML应用中的意义,希望能对读者有所启发。

人类用词汇表达一定的意义,这是件很有意思的事。比如,“模型”和“建模”这一对词汇,形式上有一字相同,意义上也密切相关;英文原词modelmodeling亦如此,形式上后者多了一个ing后缀;其实,modelmodeling词源上根本就是同一个词——model作动词时可以当“为……建模”讲。 

例子远不止这些。心理学中,“语言”和“言语”关系紧密。“语言”是一种符号系统,由词汇和语法构成。人们使用语言进行思想交流,称为“言语”,它可以分为三种形式:口头、书面、内部言语。心理学的研究表明:语言是思维的基础,并对思维具有反作用;思维对事物的反映,总是借助语言进行的;思维过程通过内部语言进行,思维结果通过口头或书面语言表现。 

统一建模语言(Unified Modeling LanguageUML)既然是一种语言,当然也会对思维有“反作用”——是促进思维还是阻碍思维,全凭UML的使用者对UML内涵的掌握程度了。

本文结合实例,说明图论思想在UML应用中的意义。希望能对读者达到“UML促进其思维”的境界,带来些许启发。

一、图的定义

顾名思义,图论就是研究图的理论。图是一种由两个集合——即一个顶点集合和一个边集合——定义的抽象数据结构。图的更形式化的定义如下:

G=(VE)是一个图,如果

(1)       V是一个非空有限集合,

(2)       EV中元素的无序对所组成的有限集合,

并把V的元素叫做图的顶点,E的元素叫做图的边。 

举个例子,下图是一个有7个顶点和5条边的图,vi标出了顶点,ei标出了边。

 

二、图的定义的UML应用——UML的图论观点

UML作为可视化建模语言,包括语法和语义两个方面。单从语法方面,用图论的眼光——把UML看作顶点和边——来学习UML,应当说是正本清源之道。下表以图论观点对UML语法进行了总结。

 

顶点

边属性

其它

用例图

参与者, 用例

关联,泛化, 包含,扩展

 

接口

包图

包, 接口

依赖, 实现

 

可嵌入类图

类图

关联, 泛化, 依赖

角色名,多重性,导航,组成符,聚集符,关联名,关联名方向

限定符, 参数化类, 关联类

对象图

对象

角色名,多重性,导航,组成符,聚集符,链名,链名方向

 

顺序图

对象

消息

消息名,条件,重复

参与者实例,生命线,激活

协作图

对象

链,

消息

消息号,所有顺序图的边(消息)属性,所有对象图的边()属性

参与者实例,位置,状态,变成流,拷贝流

构件图

构件,接口

依赖

 

可嵌入对象图

部署图

节点

连接

 

可嵌入构件图

状态图

状态

转换

条件,动作

复合状态

活动图

活动状态

完成转换

条件,分支,分叉,结合

泳道,对象流

数学中,有关“数学抽象度”的研究表明:抽象层次越高,切近事物本质越深。UML的图论观点,从更抽象的“图论”角度理解UML的语法,因此能够“切近事物本质更深”。UML 2.0即将全面到来,改动虽大,但决不会跳出图论范畴;总之,理解了UML的图论观点,对快速掌握UML新规范大有裨益,笔者的实践也证明了这一点。

三、图的定义的UML应用——关联类语法的理解

除了上面的基本总结以外,笔者发现UML中的关联类常被“误用”或“该用不用”,所以有必要谈一下。

语法方面,从图论中对图的基本定义,可以找到对关联类的“犀利”的理解。

首先,扩展一下图论的“经典”定义,如下图所示。

 

扩展之后,顶点可以由更多的“角色”来承担:除了通常的顶点外,边也可以充当顶点。这样以来,边就有如下三种情况:

l         连接顶点与顶点的边

l         连接顶点与边的边

l         连接边与边的边

然后,分析关联类本身的语法,它用到了上面扩展的第二种情况。如下图所示,关联类语法分为关联部分、类部分、关联部分和类部分之间的可视化连接部分,共三部分内容。

 

总之,虽然从语义来讲,关联类是一个独立的模型元素,但从语法角度,它既包含了关联的符号,又包含了类的符号。

四、图的定义的UML应用——说说序列图

 

值得补充说明的是,序列图中“生命线”和“激活”也是可以充当顶点的边,如下图所示(该图引用自《UML参考手册》)。

 

五、有向边

 

有向图是无向图的特殊情况,它们的定义有微妙的差异。

G=(VE)是一个有向图,如果

(1)       V是一个非空有限集合,

2  EV中元素的有序对所组成的有限集合,

并把V的元素叫做图的顶点,E的元素叫做图的有向边

举个例子,下图是一个有向图,描述的是足球赛的小组循环赛:a3场全胜,bcd3个队都是12负。

 

 

六、有向边的UML应用——依赖关系

 

静态视图是UML的基础。模型中静态视图的元素是应用中有意义的概念,这些概念包括真实世界中的概念、抽象的概念、实现方面的概念和计算机领域的概念。静态视图中的关键元素是类元及它们之间的关系;类元是描述事物的建模元素,包括类、接口和数据类型等;类元之间的关系有依赖、泛化、实现和关联等。

依赖表示两个或多个模型元素之间语义上的关系,即提供者的某些变化会要求或指示依赖关系中客户的变化。例如,下图体现了一条架构设计的基本原则:问题领域层“不依赖于”其他任何层,而其他任何层“只依赖于”问题领域层。

七、有向边的UML应用——泛化、实现和关联的依赖思想

根据依赖关系的定义——依赖表示两个或多个模型元素之间语义上的关系,即提供者的某些变化会要求或指示依赖关系中客户的变化——泛化、实现和关联也是依赖关系,它们都包含了依赖的思想。

举个例子。下面是一道极为常见的笔试题(以Java语言为例):

写出下列程序的运行结果:
public class Test {
  public static void main(String[] args) {
    Child child = new Child();
  }

class Parent {
  Parent() {
    System.out.println("to construct Parent.");
  }

class Child extends Parent {
  Child() {
    System.out.println("to construct Child.");
  } 

  Delegatee delegatee = new Delegatee();
}

class Delegatee {
  Delegatee() {
    System.out.println("to construct Delegatee.");
  }
}

这道题考的是构造函数的执行顺序,输出结果如下(以Java语言为例):

to construct Parent.
to construct Delegatee.
to construct Child.

 

其实,构造函数的执行顺序,何尝不是“依赖思想”决定的呢?具体而言,就是“被依赖的先构造,依赖于人的后构造”。继承关系包含了依赖思想,子类Child依赖父类Parent,所以Parent先于Child构造。关联关系包含了依赖思想,聚集是关联关系的一种,所以被聚集的类Delegatee先于Child构造。

 

相信至今还有人会画出类似下面的类图,它错在继承的箭头方向画反了!经过以上的讨论,我们知道了大师们把继承的箭头方向规定为指向父类有其深刻含义——它代表了依赖的方向!

 

八、有向边的UML应用——一个例子

图论是对现实世界中实际问题的高度抽象。有向图可以对具有特定依赖关系的实体群落,进行有效的抽象刻画,使人们能够忽略无关紧要的众多细节,而牢牢把握住本质性的依赖关系。

依赖关系在软件开发中的重要性,不管怎么强调都不过分。响应变化的能力往往决定一个项目的成败,而依赖关系的处理(其实不仅包括类与类等工件之间的依赖,还包括人之间的依赖和活动之间的依赖,本文不详细讨论)正是其中的关键。

著名的开放封闭原则(Open-Closed PrincipleOCP)规定,“软件实体应该是可以扩展的,但是不可修改”。本原则紧紧围绕变化展开,变化来临时,如果不必改动软件实体的源代码,就能扩充它的行为,那么这个软件实体的设计就是满足开放封闭原则的。如果我们预测到某种变化,或者某种变化发生了,我们应当创建抽象来隔离以后发生的同类变化。在Java中,这种抽象指抽象基类或接口;在C++中,这种抽象是指抽象基类或纯抽象基类。

比如,在开发一个需求跟踪工具的时候,起初可能仅需要支持保存为专有格式的“项目”文件,但后来又需要支持导出为HTML格式的网页。让我按照敏捷软件开发过程,来讲述这个故事。

最开始的设计如下图所示,CReqMatrixDoc调用CProjectSaver来保存自己。按照开放封闭原则(Open-Closed Principle),这并不是一个好的设计。但此时,所有需求就是支持“保存为专有格式的项目文件”,而且我们并没有预见到将来还需要以更多的形式保存,所以这个设计“不多不少”刚刚好。

 

后来需求发生了变化,这个工具需要支持“导出为HTML格式的网页”的特性。是的,这个需求不管是客户新提出来的,还是设计人员在上一个迭代有意忽略了,总之在这个迭代周期需求发生了变化。于是,设计人员意识到,需求跟踪工具可能需要支持多种保存策略。

看来,代码出现了臭味(Smell),需要重构(Refactoring)。让我们谨遵Martin Fowler“两顶帽子”的教诲——不要将重构和添加新功能同时进行——这一步我们仅进行重构。我们要做的就是采用依赖倒置原则(Dependency-Inversion Principle惯用的“用两个抽象依赖代替一个具体依赖”策略,重构之后的设计如下图所示。我们引入了一个接口CDocSaver,然后让CProjectSaver实现这个接口。

 

哈,新的设计满足开放封闭原则,究其原因,最关键的一点就是CProjectSaver对CDocSaver接口的单向依赖。新设计非常易于扩充,我们只需新写一个CHtmlSaver来实现接口CDocSaver,就离支持“导出为HTML格式的网页”不远了,如下图所示。咦,原来是GOF的Strategy模式。

 

 

 

版权所有:UML软件工程组织