一.UML简史
面向对象的分析与设计(OOA&D)方法的发展在80年代末至90年代中出现了一个高潮,UML是这个高潮的产物。它不仅统一了Booch、Rumbaugh和Jacobson(UML之父:三人)的表示方法,而且对其作了进一步的发展,并最终统一为业界所接受的标准建模语言。
统一建模语言(UML)的出现公认的面向对象建模语言出现于70年代中期。从1989年到1994年,其数量从不到十种增加到了五十多种,哎,百家争鸣。在众多的建模语言中,语言的创造者努力推崇自己的产品,并在实践中不断完善。但是,面向对象(OO)方法的用户并不了解不同建模语言的优缺点及其差异,因而很难根据自身应用特点选择合适的建模语言。90年代中,一批新方法出现了,其中最引人注目的是Booch 1993、OOSE和OMT-2等(看下图)。
UML是一种定义良好、易于表达、功能强大且普遍适用的建模语言。它溶入了软件工程领域的新思想、新方法和新技术。它的作用域不限于支持面向对象的分析与设计,还支持从需求分析开始的软件开发的全过程。
图1 UML的发展历程
二.UML简单结构
图2 UML结构图
根据UML的结构,UML也相关对其操作、运用和表述提供一系列的规则,该规则的表述见下面简述:
UML规则:<注:以下注解为著者观点>
四.UML的内容详述
基本概念与其他面向对象技术中的基本概念大多相同,因而,UML必然成为这些方法以及其他方法的使用者乐于采用的一种简单一致的建模语言(我已经强调了这个不同于程序设计语言);其次,UML不仅仅是上述方法(BOOCH91,OMT1和OOSE等)的简单汇合,而是在这些方法的基础上广泛征求意见,集众家之长,几经修改而成,UML扩展了现有方法的应用范围;第三,UML是标准的建模语言,而不是标准的开发过程(关于开发过程UML应用迭代过程,由三鼻祖力荐)。尽管UML的应用必然以系统的开发过程为背景,但由于不同的组织和不同的应用领域,需要采取不同的开发过程(但主要适用于迭代式开发,UML三鼻祖在统一开发过程中推荐的)。
下面我们来详细地说明关于UML的结构:
1.UML的事物
1.1结构事物
1.1.1类(Class)
我们学习过C++或者Java(面向对象的语言好象都有,但称谓是否是"类"我还不清楚)的人都知道,该语言中有类的概念。
类的定义是一组具有相同属性、操作、关系和语义的对象描述。
UML的图形表示上,类是一个矩形。
重要说明:UML图形是采用Rose工具画出,然后剪切下来的,对于以后的本书中的一切图形,都是如此而来的。
① 类的名称(Name)
类当然有名称,但也必须有一个名称;名称是一个标记,我们应该记起我们在数学一章中介绍的关于名称如何的标记的,可以是一个字符串也可以是一个数字串或者其他什么标记符号。
一般类有两种表示,一是直接表示,就是类名;一是路径表示,就是类名加路径名。就好象国外人起名字,喜欢加上父亲或爷爷的名字来表示他产生的"路径"。比如用TOMClass来表示一个类,也可以用TOMGranddadClass::TOMFatherClass::TOMClass来表示这个类。
以图示之:
② 类属性(Attribute)
光有类的名称是不完全的,徒其是个空类。
何谓属性?是以被冠名的类(事物的抽象)特性。我们仍然以农场主和羊的例子来说明:
羊的特性:有毛(叫羊毛),有皮(羊皮),有肉(羊肉)…
以此,我们画一个羊的类图:
同样我们也可以在表示属性的同时,给属性加上一些标记或属性的属性。比如说Wool是白色的,Mutton是鲜的,Sheepskin是一种皮革…等,我们可以在属性后附加如此说明。
说一句简单道理,何谓属性?属性就是文章中形容词(如果你老兄文章不识词性?),如果我们要说明一只羊,我们写了一篇文章,该文章有说明文字和被说明的对象,该说明文字就是属性,而被说明的对象有两层东西:操作和关系。
我们先来谈谈操作。
③ 类操作(operation)
何谓操作?操作是一个服务的实现,深了!操作是一个对象的动作行为。若对象的行为被对象实施或实施过或可能实施,该行为就是对象的一项操作。
比如羊的吃草就是一项操作(当然在没有吃草的时候不能算操作,但吃草就是羊操作的一种);羊的产崽也是一项操作(当然公羊不可能实施了,但若为母羊呢?),对于操作,只要群体的部分有其行为,该行为也可以算该对象的一项操作。
以图示之:
我们看见了一个清晰的类,该类由三部分组成:类名、属性和操作。
那么,我们是否理解一个类了?先别问,我们还有一个问题没有解决-关系。我先问一个简单的问题,一个羊它产崽了,那么老羊和小羊有什么关系。从属性和操作上看是"继承"关系。
在羊的产崽的过程,这个继承过程是完全的(在这里我所指的是母羊产了母羊),我们现在讨论的关系是不是老羊和小羊间的母子关系,不是的。具体的关系会被表达成:该类(羊)被销售、让其产崽,被替换(可能养羊不值钱该养牛),被屠杀等,而不是一个羊是一个另一个羊的老妈,但老妈的关系是存在,这个存在关系并不需要我们关心。
如何正确地分析类与类的关系,我们将会在"关联"一段具体分析。
关于类的属性和操作,若完全分析
④ 类职责(Responsibility)
当创建一个类时就声明该类的所有对象具有相同种类的状态和行为。在理解上类的属性和操作正是完成该类的职责的说明或特征。
举例示之:
该WoolClass类负责说明羊毛的颜色、长度、质量和加工方法。
如此就很清楚了,类属性和类操作是负责该类的特性的描述和行为的执行(此时程序编写者就可以拿程序设计中的类和此比较,观察一下细微的区别)。如何说明该类(该问题)就是语义问题。
一般情况是如何表述类的职责的,在类图的矩形框中再加一栏来说明类的职责:
1.1. 2接口(Interface)
接口就是描述类或构件的一个服务()的操作。
如何理解该描述,就好比我们造房子,我们把门呀,窗呀全造地完美无缺,可惜呀我们使用油灯的时代结束了,我们需要拉电线,怎么拉,没有办法了,随便拉吧?太难看了!
何为接口,就是我们为以后拉电线的一个穿墙套管。
再把例子说下去。
我们给了电线留了穿墙套管,但是我们又使用了电话,网络了,强电和弱电不能共用同一套穿墙套管?怎么办?我们还必须给电话线,网络线留穿墙套管,如此变化是不是设计人员可以在当初预料的到呢?
即使,使用了网络线的穿墙套管,那么前两年使用的是同轴电缆,现在使用的是超五类双绞线,还得换?
是不是如此的接口太难留了?是的。
如何解决,重构(目前企业使用较为多的方法)!
①接口的名称
接口的图形表示:一是带有关键字《interface》的矩形表示,接口支持的操作在操作分栏中(如同类图)。
以图示之:
该接口图的右边是一个构件图,目前我们没有叙述,暂不以文档示之。
二类是以小圆圈,接口的名称位于小圆圈的下方。圆圈符号用实线与支持接口的类或其他元素相连,它还可以连向高层的容器,如包。
圆圈表示法不表示接口支持的操作,其操作由接口的矩形列表表示。虚箭头表示类使用接口中声明的操作,但客户类并不需要接口的所有操作。其虚箭头将接口和使用其操作的类连接起来,箭头指向圆圈。
说明:上图表示的是右边的接口的提供者给左边接口的客户使用的。
同样,接口的名称也有简单名和路径名之分。
②接口的作用
接口用于说明类或构件的某种服务的操作集合,并定义了该服务的实现。
接口用于一组操作名,并说明其特征标记和效用(我解释一下这个词,其实质是问题产生的影响),而不是结构。
接口不为类或构件的操作提供实现。
接口的操作列表可以包括类和构件的预处理的信号。
接口为一组共同实现系统或部分系统的部分行为命名。
接口参与关联,但不能作为关联的出发点。(将在关联中详述)
接口可以泛化元素,子接口继承祖先的全部操作并可以有新的操作,实现则被视为行为继承。(将在泛化中详述)
③接口的理解
接口是一个事物(该事物是类或构件)的内部和外部通讯,从数据的初始化到数据的输入输出的实现,接口在中间其"关卡"作用。一将数据从一端取得发给另一端;一是检验数据的合法性,并监督取什么数据和怎样取数据。
在上面建房子的例子中,我们把房内的线全部布置完毕,此时有两种可能,假如室外有电话线,还有宽带接入线,我们选择:一是选择电话线,采用Modem拨号上网的方式,费用低,带窄;二是电话线和宽带线全部接入房中。
这样,第一种方式我们仅需要提供一种电话线的接入接口,而第二种我们需要提供两种接口,其采用的接入方式也不同,线的类别不同。
好了,接口的定义就明显了,接口的两端事物(类或构件)服务是需要电话线和上网线(内部需要是打电话和上网),接口说明了该服务的集合;接口定义的是线(电话线和上网线)的接入,线的接入正是可以打电话和上网的实现;但该接口不能打电话或上网,它仅仅提供一个上网出口?quot;关卡",该关卡定义两端数据交换的类型和原则。
接口的客户又是怎么回事了,情况如此:假如电话线突然断线了,挂了报修电话,电信局来人修(当然宽带有问题,电信有得来人,我用的是电信的),好了,测试人员带来的测试仪器向接口上一挂,便可以测试线路是否畅通,那么我们给测试挂测试仪器的功能就是接口的客户。当然该客户也可以采用一个构件来描述。
1.1. 3协作(collaboration)
协作描述了在一定的语境中一组对象以及实现某些行为的这些对象间的相互作用。
协作中有在运行时被对象和连接占用的槽(我不知道读者会不会联想到操作系统上有个邮件槽和管道的名词,其意同矣!),协作槽也叫角色,角色描述了协作中对象或连接的目的。
在汉语中,协作一词是大家相互帮助,一起做某件事,但该词是译词。虽然不苟同,但相差无几,看collaboration也是这个意思。
协作是动作过程中产生的一类类元。如果我们没有做什么,我们如何谈其协作,在工作的基础上才有协作的可能。所以该节到协作图一节再来详述。
1.1. 4用例(user case)
这是让我恐慌的一段,因为没有一个系统是孤立而存在的,任何一个有意义的系统都是为我们亲爱的客户而准备。其实我把这个客户扩大化了,我犯"浮夸"之嫌,客户是什么,如果我们做一个自用的测试系统,那么客户是谁?是我们,那是当然的。
在设计系统或使用系统时,都会有人参与其中,参与其中都做些事,那么这些人(actor)和事就是用例。
user case在一些译书,常被译为"用况",况者何意?情况?状况?有人参与的事物我个人认为应该为"案例",我觉得应该为"用例"。
如果我们为农场主定制一套软件,精心设计之处,我们应该明白农场主的需要什么?农场的羊很少,它可能需要一个手持式的计算器就可以完全地解决问题,当然如果该计算器中需要软件的话,我们可以为之编制;但农场主的羊如果很多,他有好几个牧场,每个牧场都有不同先进的管理设备,那么这位农场主可能需要一个设备运行情况的信息采集装置,我们可以为之定做一个信息采集、集中、显示和分析的一套软件;如果该农场主有一批农业学家为之工作,该农业学家们可能需要天气数据、牧场草皮的情况、羊群中大小的情况分布从而分析羊群的成长情况、抗病能力…等等根据不同的情况,我们来分析该"例子",需要考虑各类不同的数据,并针对该类的情况分析该软件的构架。
创建一个例子的用例(图),我们需要知道其主要因素是我们在创建中可以不详述该用户的用例是如何被实现的!就是系统创建的用例是"大概"、"扼要"地描述该"例子"(此时是指被描述的实际情况)。
① 用例的名称
用例(use case)是一组动作序列(包括它的变- - -衍生物)的描述,系统执行该动作序列(关于序列的理解读者可参见第一章)来为参与者产生一个可观测的结果值。
故,用例的名称是动作的序列名称,同其类名,也有简单名和路径名之分。路径名中用例名的前面是包名。如:
PackageName::UsecaseName
用例的图形表示有两种,一种是椭圆内部标上用例名,该名称可以是简单名也可以是路径名。
用例和类、接口一样是有操作和属性的,如果需要表示出用例的操作或属性,我们可以用带有《use case》的矩形框(类元)表示。
② 用例的表示法和关联
一. 用例和参与者的关联
用例因为参与者的存在而被称谓"用"例,参与者(就是人)与用例间的关联表明参与者与系统实例或类元实例之间的通讯。该通讯是要求型的,表明参与者对系统有那些要求,即意味着系统可以为参与者提供那些服务。
用例对该服务的实现,就是对用例的实现。
如右图即为参与者error.cao先生来计算年终羊的数量,从此也可以看出来,error.cao 先生是个农场主,他关心的是年终羊的数量,但此时没有到年终,他只可以预测年终羊的数量,此时他使用了ForecaseAccount的操作方法。
如此,我们即可以了解用例和参与者的关系,在一般情况下,我个人的理解是:参与者就是用例者。
二. 用例和状态机、活动图的关系
我们还没有认识状态机和活动图,故等等再看。
三. 用例扩展点表示法
扩展点是一个用例内部的命名实体。描述了来自其他用例的用例动作序列可以被插的位置。扩展点引用了用例行为顺序中的一个或一组位置,提供扩展和行为顺序文本之间的一个间接级别。对于扩展点的扩展关系可以被独立地改变,如图所示的AccountSheep用例和DatetimePhase用例间的关系是扩展关系,该关系从AccountSheep用例的操作或属性上,并不影响表述(文档化和实现)。
右图是用例的关系图,在AccountSheep的用例的说明下表示有一个扩展点,该扩展点用《extend》来表示该关系,同时该扩展点可以在用例区域里被列举出来,标题为extension points,如DatetimePahse用例。
泛化关系用箭头表示,从子用例到父用例的一条实线,一个实三角箭头(该图采用Rose画的,看上去象个空心的)指向父用例。
先简单说一下泛化,先认识一下"同化"一词,其意为让其与自相同,至少类别是相同的,在图论称是同构的。好,那么泛化呢?即让其拓扑!
使用数学语言很简单就分析,各个术语间的区别和联系,到现在你应该有点知道,我为什么苦口婆心来说明数学的分析的重要性的,别急,后面看到的优点还多呢!
用例被泛化成一个或多个子用例,这些子用例代表父用例更多更明确的形式,但应该注意,这些明确的形式相对父用例来说是有方向性,即说明子用例仅仅在父用例的某部分操作或属性上进行泛化,既是如此一句:同一父例的不同特化是独立的(当然该独立是有方向性的)。
当然,子用例继承了父用例的所有操作和属性。
1.1.5 主动类
何谓"主动",是"Active"活动之意,但为了赋予它的有些"生灵"特性,叫为主动也好。
从理解的角度上讲,我们应该如此的定义:一个被定义有"主观能动性"类为主动类;当然,在主动类的另一面,有其被动类,定义应该为:一个定义为被主动类操作的类为被动类。
主动类的好处,应该与参与者的动作特性密切相关。在以上定义的类Sheep是否为主动类?如何描述?
在不同的用例中,我们会得到不同的结果,其类的属性和操作的也不完全相同。我们的用例是农场主来计算羊的总值,好,是羊的平均价格乘以羊的数量,此时来定义Sheep这个类时,我们可能会定义一个属性有:每只羊的平均产毛(量),产肉(量),产乳(量),产崽(量)…如此的计算应该符合农场主的要求,我们可以计算出羊的总价值,对于类Sheep的操作几乎可以没有(注意这一点);假如农场主需要控制羊的产崽量,我们提供一个类,该类属性应该有:可产崽的羊的数量,平均每只羊的产崽数量,我们为之再添加一项操作:产崽(),即使有以上的两个属性,那么羊若不产崽呢?数量还不是依然为空,但主要是我们并不完全需要数量这个数学符号,而是需要决定权的一个参数。
在不同的用例中,我们给类Sheep的定义不同(我们可以在此做一个基类,并从中继承,这个以后在详细建模中讨论),我们就得到了一个不同性质的类,在第一个性质中,类Sheep不是主动类,也不是被动类;而在第二个用例中类Sheep是主动类。因为产崽是它的本能,是属于主动的性质。
在类的操作中,若在该用例的情况,无须外加因素只是类本身自动现实该过程的类是主动类。
我举的例子中,CreateSon()(我的意思是下崽,我喜欢如此定义)的操作是一个羊自身操作过程。
1.1. 6构件(component)
构件定义:系统中遵从一组接口且提供其实现的物理的、可替换的部分。对系统的物理方面建模时,它是一个重要的构造块。
构件的名称和类的名称的命名法则很是相似,有简单名和路径名之分。构件的描述如上图所示。
若构件的定义良好,该构件不直接依赖于构件的所支持的接口,在这种情况下,系统中的一个构件可以被支持正确接口的其他构件所替代。
构件的表示法是采用带有两个标签的矩形。
以上是UML标准中如此声明的,我们在建筑中中分析该问题容易理解建模中的"构件",何谓构件?在建筑中,我们容易理解墙、门、窗户这些真实事物的在建筑中是构成建筑物的一个组成部分,我们可以把这个组成部分叫着构造块。
我们先来研究一下建筑物中的"门",该门是一个构件,它起到一个什么作用,首先它是该建筑物的一部分,如果我们不安装门,我门的墙上必定有个孔,其实有个孔倒不是一个很重要的事,最重要的是我们没有一个进入房间的入口。
我们在谈到接口的时候,我们谈到接口是通道"关卡",那么这个关卡设置在什么地方,我现在告诉你,这个"关卡"就是设置在构件上,假如我们认为门上的"猫眼"是一个接口的话,那么这个"猫眼"接口就是该构件---门的接口。
但若"猫眼"不是接口,但猫眼它确实在门上,那么这个时候,"猫眼"是什么角色呢?我们称之为构件上的对象(object)。我们以门上锁(Key)为例,看下例:
门是一个带有一个Key接口的构件。
关于构件上的对象实例。我们在"节点"上讨论。
1.1. 7节点(Node)
位置(Location)定义:一个运行时实体在环境中的物理放置,如分布式环境中的对象或分栏。在UML中,位置是分散的,位置的单位是节点。
节点定义:运行时的物理对象,代表一个计算机的资源,通常至少有个存储空间和执行能力。运行时对象和运行时构件实例可以驻留在节点上。
物理节点有很多的特性:能力、吞吐量、可靠性等,UML没有预定义这些特性,但它们可以在UML模型中用构造型或标记值建立。
节点是实现视图中的继承部分,不属于分析视图。虽然节点类型有重要意义,但通常各个节点的类型是匿名的。
节点是一种类元,可以有属性。
以上是UML标准定义的节点的语义。
节点在我们的计算机的应用上,属于什么角色呢?再考察一下节点的定义:(软件)运行时的物理对象->我们可以理解为计算机硬件或计算机本身,当然,也可以表示路由器、终端、以及一些通讯采集卡(内含CPU存储芯片)。
我记起台湾的MOXA公司在南京举办一个研习班,我参加的,他们就是做如此终端采集服务器。
节点名称的定义:Node-Type即节点标识+节点的类型的标识。
举个如下的例子进行说明:
节点的定义描述在软件运行的环境本身的区域性的"域",你组建一个网络(软件运行的环境),你可以把每个计算机看成一个节点,若对计算机进行分类,那么一个域可以被看成一个节点,但对于我们也可以把存储设备、处理器等看成节点。
这个时候,我们对节点的定义就会相关到"系统"上去,在不同系统表述上,我们看待节点的也是不同的,主要是对节点相对于系统本身地描述。节点是系统上节点,所以针对不同的系统(或对系统进行细化后模块)其构件(节点)也不同,所以构建方式也不同。采用构建方法的不同是针对不同层次的构件,主要是根据系统(不同层次)的用况图来构建(或定义)系统的构件(也可以描述成节点)。
所谓的节点定义就明朗多了。
1.2行为事物
1.2.1交互(Interaction)
交互是协作中的一个消息集合,这些消息被类元角色通过关联角色交换。当协作在运行时,受类元角色约束的对象通过受关联角色约束的连接交换消息实例。交互作用可对操作的执行、用例或其他行为实体建模。
消息是两个对象之间的单路通信,从发送者到接收者的控制流。消息具有用于在对象间传值的参数。
消息序列从不同方向去理解可得两种图:顺序图(谁先谁后)和协作图(谁和谁交互信息)。
如上的消息序列表示出来是很重要的,主要由顺序图和协作图来关联编写源码。
如何理解交互,作为行为事物,交互是一组对象之间为了完成一项任务(如操作)而进行通讯的一系列消息交换的行为。首先交互在一组对象之间进行;其次,交换是为了完成一项任务;最后,交互进行一系列消息交换。
比如,农场主想计算羊的一年的产崽量,他首先知道找那些人询问;其次,他也知道问什么;最后,他问(一些饲养人员)了一些问题。当这个交互过程结束后,他计算出来羊的数量(可能该计算已经在消息的交互过程中进行了)。
从生活上理解,交互一个信息来往的过程,而在UML中,交互是这些信息交换过程(行为方面)的集合。
交互的表示在顺序图、协作图和活动图中。
1.2.2状态机(state machine)
状态机是一个状态和转换的图,描述了类元实例对事件接收的响应。状态机可以附属于某个类元(类或用例),还可以附属于协作和方法,状态机附于的元素被称为状态机的主机。
状态机的执行:状态机在某个时刻处理某个事件而在处理另外的事件之前必须完成这个事件的结果。
执行的语义描述(较生涩):在任何时候都存在一个或者多个状态机的活动的状态。如果一个状态是活动的,那么离开这个状态的转换(是转换被激发)可能会激发,引起一个动作的执行,并使得另一个状态或位于初始状态位置的状态激活(下一个状态处于初始状态)。
状态机的子状态(也生涩):状态机的结构和状态机的转换可以对并发活动的状态施加约束。即如果一个顺序状态,则只有一个直接的互斥子状态必须处于活动;而一个并发组成状态处于活动,则每个直接子状态都必须处于活动。
关于状态机的语义,我先生涩地说明如下,但其如何理解?
状态机它是说明它(状态机)的对象在它(对象)的生命期中事件的所经历的状态序列以及它们对那些事件的响应。
解释一下几个名词:
1.状态(state):是指在对象的生命期中的一个条件或状况,在此期间对象将满足某些条件、执行某些活动或等待某些事件。
2.事件(event)是对一个在时间和空间上占有一定位置的有意义的事情的规格说明。在状态机中,一个事件是一次激发的产生,激发可以触发一个状态转换。
3.转换(transition)是两个状态之间的一种关系,它指明对象在第一个状态中执行的一定的动作,并当特定事件或特定条件满足时进入第二个状态。
一个活动(activity)是状态机中进行的非原子执行。
一个动作(action)是一个引起模型状态改变或值的返回的可执行的原子计算。
下面来说明这几个概念:
状态机说白了很简单,说一个最常见的例子,我们下班回家这件事,人(假如是我error.cao)就是一个对象,我们来考察一下几个状态:
1.到下班时间了,收拾东西准备回家(不考虑加班)。
2.开始等电梯。
3.到了楼下。(发现没带家里钥匙,上楼拿。)
4.上楼。
4.去公交等车。
5.乘公共汽车去菜场。
6.买菜
7.回到家
那么事件呢?
1.下班时间到了(准备下班)。
2.电梯到(上电梯)
3.电梯到楼下(下电梯)
3.发现没有家里钥匙(去拿钥匙)。
4.自己要乘公共汽车到了(上车)。
5.公共汽车到站(下车)。
6.忽然想起家里没菜(去买菜)。
那么何为转换:有个事件(发现没有家里钥匙),状态一(到楼下)转换到状态二(上楼),当然这个转换是对象(error.cao)的行为。此时再提一下初态和终态的概念:初态和终态并不针对转换,而是针对对象,该状态一(到楼下)不是初态,初态是到了下班时间;终态自然也不是状态二(上楼),而是回到家。那么该转换的状态一是什么呢?是该转换的源状态(source state),状态二呢?是该转换目标状态(target state)。
关于非触发转换我不太赞成这个说法,仅仅可以说是它被隐式地被触发了,比如,以上的事件二,我们可以不表述它是个事件,但从状态二(开始等电梯)到状态三(到了楼下)就是个隐式地被触发,它有没有事件,当然有,仅仅是我们没有考虑(这些事件可以忽略)。
在转换中还有个术语,叫监护条件,何谓监护条件?看一下事件一(下班时间到了),我们必须定义一个时间,然后由时钟告诉你(是你拿时钟上的时间和下班时间比较)时间到了,好了,时间到该下班了(事件产生了),事件产生了,对象就发生状态的转换。
当然各个状态本身包含如下内容:(括号表示该状态可以没有)
名称:就是名字,状态的名字。
进入/退出动作:对象本身的一个操作,比如在电梯里是一个状态的话,哪我们进电梯和出电梯就是状态---在电梯里---的进入/退出动作。
内部转换:比如我们在去等电梯的时候发现钥匙没带,此时我们不用在等电梯的以后状态是再有事件触发,在准备下班的状态上我们就去拿钥匙了,对于对象本身,前后两次的根本状态不一样,一个是有钥匙,一个是没有钥匙。
(子状态) :如果我们描述该对象在电梯里说话,抽烟(一般电梯不许)等状态时,该状态就是该对象状态---在电梯里---状态的子状态。
(延迟事件) :现在不立即产生的事件,该事件是在一段时间以后才产生的事件。
说了半天,何谓状态机?对象从初态向终态过度的过程中,响应一系列的事件而经历的状态,同时包括对该事件的响应。首先,它不是这个过程,但它描述这个过程,是该对象在这一系列过程中的行为。
以上描述的就是我们每天下班的所做的事,这不是一个用例,但可以说是一个对象多个用例表示(一个用例可能描述不完如此只多的状态及其响应)。
若我们说到UML图的关系时大家会豁然开朗,因为我们的目标很明确,就是开发出我们客户需求的软件,我们采用UML来对系统进行建模,UML的最终结果是一个采用各种图的形式来把各个类元间的关系表达出来,通过表达出来的类元关系,我们来编制软件代码。
1. 3分组事物
1.3.1包(package)
包的定义:用于把元素组织成组的通用机制。
包在理解上和构件(component)有协同之处,构件是组成事物的元素,我们定义的包是一个构件的抽象化的概念,是把类元按照一定的规则分成组(也可以称为模块)。我原先的最初想法是package =component(s),后发现没有如此简单,package =component(s)+规则,这个规则不是条文,是构架在组件之上的思想抽象,而这个抽象恰恰是我们包的定义。
包主要是包含其他元素,如类、接口、构件、节点、协作、用例和图,当然也可以包含其他的包。
1. 4注释事物
1.4. 1注解(note)
附加定义性告诉被注解对象的性质、特征、用途等。
如果这样理解注解似乎是可以的,但描述性字符太少,免不了心里不塌实,但如何理解注解呢?就是说明性的文字。
说明什么,什么都可以被说明,只要是这个世界上存在的事物就可以被说明。
但在UML的定义中,这些描述性的文字常常被隐藏起来,并不直接地表示成为注解(事物),作者本人也是如此认为,注解最好的表述方式是隐性。作为被描述的事物本身由于没有直观的表述注解,其脉络是清晰的。
其实这个东西不用来解释,我记得张五常先生的《经济解释》中首先提及的套套逻辑即是如此,因为我们采用的注解的方式来注?quot;注解"这个词。
1.5关系事物
关系是UML中最难表示的一"件"东西(我自己认为,但不知道专家们如何看),难在那里,它就是从系统分析到系统构架的关键。
关于关系我会在下一章详细地说明,其实软件开发仔细想就是我们对现实事物的描述,而这个描述就是关系,其现实事物就是对象(Object)。
以下说明UML中几个典型的关系。
1.5. 1依赖(dependency)
一个元素(被依赖事物的提供者)的变化将影响到另一个元素(依赖事物的接收者),或向其(接收者)提供信息。
关于依赖的语义,我举个简单的例子,一个小孩(依赖事物)是没有获取食物的能力的行为,他的生存就是依赖于他的父母(被依赖事物)对他的抚养(依赖方式)。故依赖被这样定义:一个事物为了达到某个目的,而采用一种依赖方式依赖于被依赖事物。
依赖的形式可能是多样的,这样我们就用依赖关系来表述它,针对不同的依赖的形式,依赖关系有不同的变体
(varieties):
<1>抽象(abstraction):从一个对象中提取一些特性,并用类方法表示。
<2>绑定(binding):为模板参数指定值,以定义一个新的模板元素。
<3>组合(combination):对不同类或包进行性质相似融合。
<4>许可(permission):允许另一个对象对本对象的访问。
<5>使用(usage):声明使用一个模型元素需要用到已存在的另一个模型元素,这样才能正确实现使用者的功能(包括调用、实例化、参数、发送)。
<6>跟踪(trace):声明不同模型中元素的之间的存在一些连接。
<7>访问或连接(access):允许一个包访问另一个包的内容。
<8>调用(call):声明一个类调用其他类的操作的方法。
<9>导出(derive):声明一个实例可从另一个实例导出。
<10>友员(friend):允许一个元素访问另一个元素,不管被访问的元素的具有可见性。
<11>引入(import):允许一个包访问另一个包的内容并被访问组成部分增加别名。
<12>实例(instantitate):关于一个类的方法创建了另一个类的实例声明。
<13>参数(parameter):一个操作和它参数之间的关系。
<14>实现(realize):说明和其实之间的关系。
<15>精化(refine):声明具有两个不同语义层次上的元素之间的映射。
<16>发送(send):信号发送者和信号接收者之间的关系。
UML本身没有提供如此之多的定义,作者根据自身对UML的认识,添加一些,这些东西会在下一章中详细描述。
1.5.2关联(association)
关联是两个或多个特定类之间的关系,它描述了这些类元的实例的联系。参与其中的类元在关联内的位置有序。在一个关联中同一个类可以出现在多个位置上,关联的每一个实例链是引用对象的有序表,关联的外延即这种链的一个集合。在链集合中给定对象可以出现多次,或者在关联的定义允许的情况下可以在同一链中(不同的位置)出现多次。关联将一个组织在一起。如果没有关联,那只是一个无连接类集合。
关联可以有一个名称,但是它的大部分描述建立在关联端点中,每个端点描述了关联中类对象的参与。关联端点只是关联描述的一部分,不是可区分的语义或可用符号表示的概念。
关联名称在包含包的所有关联和类中它必须是唯一的。若名称不唯一,关联的端点并不明确。关联的端点定义在关联位置中给定位置的一个类(角色的参与)。同一个类可以出现在出现在多个位置,但位置是不可以交换的,这个位置的关系就是关联?quot;关系"。当然位置的端点也可以是其他情况下的类元。
在UML 的语义中,位置的关系(类元的关联)称为链。链在系统执行过程中可以被创建和销毁,服从每个关联端点可变性的限制(类元本身的对应或所属关系)。
以下的关联图是两个参数类,简单的关联就是羊毛的加工是由操作者来实现的,一个操作者可以加工很多羊毛,而羊毛在一个步骤仅仅需要一个加工者(不考虑多个步骤)。
1.5. 3泛化(Generalization)
泛化是什么?是一个较广泛的元素和一个较特殊元素之间的类元关系。较特殊的元素完整地包含了较广泛元素,并含有更多的信息。较特殊的元素的实例可以用于任何使用较广泛的元素的地方。
泛化是两个同类的可泛化元素(如类、包或其他元素)之间的关系,其中一个元素被称为父,另一个为子。对类而言,父类称为超类,子类称为子类。父类说明的直接实例带有所有子类的共同特性,子所说明的实例是上述实例的对象。
在类(这里指的是C++或JAVA中面向对象编程中类)的继承中,泛化的概念被广泛地应用,其实所谓的泛化就是将特殊的事物抽象出来,主要是面对这些事物的特征,将这些特征和与之相关的事物进行比较,将其共同的特征抽取出来,进行归纳,所得到的结果就是我们需要表现出的该事物的泛化特性。
在类的实例中,最常见的一个例子就是图形的问题,如描述三角形,或描述正方形,或椭圆等,这些都是图形,好了我从三角形、正方形、椭圆等中抽取该部分的共同特征,将其描述,这个就得到一个图形(从三角形泛化出的结果)描述。
学习JAVA或C++的兄弟们一看便知,问题的关键就是基类和继承类的关系。但关于参数类的泛化成一般的类(这个关系比较模糊),我们将在下一章的关系中进行更详细的描述。
1.5.4实现(Implementation)
1.定义某事物是如何构造的、计算的。例如,类是类型的实现,方法是操作的实现。实现和说明之间是实现关系。
2.用可执行的媒体(如程序设计语言、数据库、数字化硬件)描述系统功能的一个步骤。对实现而言,必须产生下层的决策以使设计适合具体的实现,并与环境相适应(每种语言有各自的限制)。如果设计得好、实现任何决策不会影响系统的全局。这一步由实现层模型捕捉。特别是静态视图和代码。
实现是一种关系,是一种将模型元素(如类)与另一种模型元素(如接口)连接起来,其中接口只是行为的说明而不是结构或者实现。客户必须至少支持提供者的所有操作(通过继承或者直接声明)。虽然实现关系意味着有像接口这样的说明元素,它也可以用一个具体的实现元素来暗示它的说明(而不是它的实现)必须被支持。例如,这可以用来表示类的一个优化形式和一个简单低效的形式关系。
泛化和实现关系都可以将一般描述与具体的描述联系起来。泛化将在同一个语义层内的元素连接起来,而且在同一模型内。实现关系将在不同的语义层内元素连接起来,(接口或类)并且通常建立在不同的模型内。
1.5图
1.5.1类图(Class diagram)
类图的语义:
类图是静态视图的图形表达方式,表示声明的(静态的模型元素),如类、类型及其他内容及相互关系。类图可以表示包的视图,包含嵌套包的符号。类图包含一些具体的行为元素,如操作他们的动态特征是在其他图中表示的,如状态图和协作图。
表示法:
类图是用图形方式表示的静态视图。通常,为了表示一个完整的静态视图,需要几个类图(类图这个时候要讲究关联性,如逻辑划分)。每个独立的类图需要说明基础模型中的划分,即是某些逻辑划分,如包是构成该图的自然边界。
类图的表示是一个简单的表示:
(我们还是来举个我们原先用过的例子来说明下面的各个图例。在述说状态的时候,我们举了一个下班拿钥匙的例子,这次,我们换一下说明方式,假如一位经理下班后,到了楼下,发现钥匙没有拿下来,这时他让秘书上去拿。最后秘书拿到钥匙后交给经理,经理拿到钥匙回家)
1.5.2对象图(Object diagram)
对象图的语义:
对象图显示某些时刻对象和对象之间的关系,比如对象是类的实体,那么对象就是将类图中的类换成该类的实体-对象,那么,这个图就是对象图。对象图和协作图相关,协作图显示处于语境中的对象模型(类元角色)。
对象图的表示法:
对于对象图无需提供单独的形式。类图中就包含了对象,所以只有对象而无类的类图就是一个"对象图"(和语义的描述一致)。然而,"对象图" 这一个术语仅仅在特定的环境下才很有用。
对象图不显示系统的演化过程,他仅仅是对象的关系等的静态描述。
1.5.3用例图(User case diagram)
用例图语义:
表示处于同一个系统中参与者和用例之间的关系。
用例图表示:
用例图是包括参与者、由系统边界(一个矩形)封闭的一组用例,参与者和用例之间的关联、用例间关系以及参与者的泛化的图。用例图表示来自用例模型的元素。
1.5.4顺序图(Sequence diagram)
顺序图语义:
以时间顺序显示对象的交互的图,实际上,它显示了参与交互对象的和所交换消息的顺序。也是以时间为次序的对象之间的通讯的集合。不同于协作图,顺序图仅仅时间关系,而对象关系(准确地讲应该是对象的时间顺序关系)。
顺序图表示方法:
顺序图有两个方向,就是我们所说的两维,垂直方向代表时间,水平方向代表参与交换的对象(其实含有先后次序),无论水平或垂直方向先后次序并没有规定,谁先谁后都可以。知道操作系统机制的朋友都有一个概念-就是消息机制为驱动,自然其他的应用程序也差不多,顺序的关系就是消息方向,这个方向对参加交互的对象也是有次序的,每次一个对象参与消息机制。
当然,对象在进程(一个进程有n个对象,采用new(object)声明即可)中也是有生命期(随着delete(object)方法生命就结束了),那么这个线就可以不在画下去。那么对于一个外部对象(不在内部生成,也不在进程结束时消失),又如何描述呢?同样。
每个消息显示为一个从发送消息的对象的生命线到接收消息的对象的生命线的水平箭头。在箭头相对空白处放置一个标号以表示消息被发送的时间或其他的约束条件。
1.5.5协作图(Collaboration diagram)
在前面对协作讲述较少,如何解决?没有办法欠下来的就补上!
协作图语义:
协作图表示角色间交互的视图,即,协作中实例及其链。与顺序图不同,协作图明确地表示了角色之间的关系。另一方面,协作图也不将时间作为单独的维来表示,所以必须使用顺序号来判断消息的顺序以及并行线程。顺序图和协作图表达的是类似的信息(使用不同的方法表达)。
协作图表示:
我们先来澄清一下前一段对协作没有说清楚的问题,然后再说协作图。
<以下是作者对上面描述的协作的补充>
协作的语义:在一组给定的对象(这些对象在构造构件图时进行说明)和给定的环境(描述时称为语境),为了完成某个目标而交换消息。从而实现了一种行为。要理解设计机制,应该重点着眼于实现一个或一组目标而涉及的对象和消息。它们在更大的系统中也用于完成其他目标。使对象和链共同工作以实现某种目的而进行的安排称为协作。在协作中实现的行为的消息序列称为交互。
协作由静态部分和动态部分组成。静态部分描述在协作实例中对象和链可能承担的角色;动态部分包含一个或多个动态交互,表示在执行计算过程中不同时间里协作中的消息流。在协作中流动的消息流可以选用状态机来进行描述,状态机将规定合法的行为顺序。而状态机中的事件代表协作中各角色间的消息交换。
协作由角色组成,同时角色也仅仅在协作中才有意义,在协作之外无意义。
在程序的实施过程中,我们经常考虑的问题就是对象的运行期(Runtime)问题,其始终就是对象(此处看成角色)在运行期的协作(这个协作我们称为运行期绑定)。
在上面我们已经说了关于协作的消息流,读者再查看一下我们曾叙述状态机的时候,也对事件或消息谈了许多。事件也是一种消息,不过这个消息在整体上将给应用性程序或协作的什么角色一个状态转换触发(这个地方是windows等操作系统"以事件驱动之"核心描述)。如果没有明白再看看下班的那个例子。
不过在协作中还有另外两个概念:一是约束,一是资源。
从本质上描述,假如对待的是消息的话,资源就是提供消息的类元,而约束就是提供什么样的消息。
协作图:
我个人认为协作图该有两类,一是操作协作图,一是实体协作图。
操作的协作图讲关系,而实体的协作图讲消息。对于图的本质是一致的都是角色间的交互。
1.5.6状态图(Stat chart diagram)
状态图语义:
显示一个状态机(包括简单状态、转换、嵌套组成状态)的图。这个视图包括状态机。关于状态机我建议你再回头看一下状态机的说明。
状态图表示法:
在状态机一段的说明时我们举例的就是一个状态图。
参见原先述说状态时的状态图
1.5. 7活动图(Activity diagram)
活动图语义:
活动图是状态机一个特例,在该状态机中所有的或大部分的状态都处于活动状态或动作状态,所有或大部分的转换由源状态中活动的完成所触发。
活动图表示一个程序或工作流。工作流是被活动图所建模的过程的例子。活动图通常出现在设计的前期,即在所有实现决定前出现,特别是在对象被指定执行所有的活动前,其状态代表活动的执行,就象一个计算机或真实世界不间断的操作,而转换由状态内活动的完成来触发(若有约束条件,可能有几个可能不同的出口)。
活动图是强调计算过程中顺序的和并发步骤的状态机。
几个概念:
动态并发性:具有动态并发性的活动状态表示并发执行多个独立的计算。活动与一个参量表集合同时被调用。集合中的没一个成员都是活动的并行调用的参量表。调用是相互独立的,当所有的调用完成时,活动结束并触发它的完成转换。
对象流:有时,查看一下操作和作为它的参量值或结果的对象之间的关系有好处的。一个操作的输入和输出可以表示成一个对象流状态。它是一个状态的构造型,表示在计算过程中特定点的给定类的对象流状态。它是一个状态的构造型,表示在计算过程中特定点给定类的对象存在。
由动作输入或输出的对象可以表示为对象符号。符号表示处于计算中某一点的对象,在该点对象适合作为输入或输出。虚线箭头表示从活动状态到作为活动输出之一的对象流的输出转换。虚线箭头也可以表示从对象流到用这个对象作为输入的活动状态的输入转换,通常,同一个对象可以作为一个活动的输出和多个后继活动的输入。
状态类:同一个对象被一些改变它的状态的后继活动所控制。为了更加准确,对象可以在图中出现多次。每次出现表示它生命中的不同状态。为了区分同一个对象的多次出现,每个点的对象的状态可以放在方括弧内附加类旁边。
泳道:活动图中的活动可以按照不同准则划分为几组。每个组代表活动职责的一些有意义的部分,例如,商业组织负责给定工作流的某一步。根据它们的图形表示法特征,每个组称为泳道。为了说明这个概念,拿泳道来表示并不直观,我一般这样看泳道的:拿业务上的流程来说明问题,比如填单是一个步骤(也可能是几个步骤,不一定连续),财务审核是一个步骤(也可能是几个步骤,也不一定连续),仓库也是一个步骤,其中中间的过程还可能转来转去的;好了,我们把业务、财务、仓库分为三个部分,这每个部分可以称为泳道。
每个泳道里有几个步骤,我们可以看成对象,对象间逻辑、交互等关系,我们可以在顺序图中表示。其中关系的具体实施我将后几章详细说明。
延迟事件:当其他的活动进行时,有一种事件必须为了晚一些使用而延迟(没有立即使用的事件会被遗失)。延迟事件是被放置在内部队列中,直到它被使用或抛弃的事件。如果在状态或活动中发生延迟事件,则该状态或活动将对它们进行说明,其他的事件必须被立即激发。如果几个转换为隐含的,则它们中的哪一个会被激发并不明确,且施行一条规则以选择一个要激发的转换是语义变更点。 活动图的表示法:
活动图是状态机的一种,但是几种简单表示法(也可认为是几个分支)也适用于活动图,如:活动状态、分支、合并、泳道、对象流状态、状态、延迟事件等。
1.5. 8构件图(Component diagram )
构件图语义:
构件图表明软件之间的依赖关系,包括源代码构件、二进制代码构件和可执行代码构件。软件模块可以用一个构件来表示。有些构件存在于编译时,有些存在于链接时,有些存在于执行时,有些在多种场合存在。一个编译时构件只在编译时有意义。
构件图只有描述符形式,没有实例形式。要表示构件实例,应使用部署图。
构件图表示:
构件图表示了构件类元,以及其中定义的类(或其他的类元)和构件间关系。构件类元还可以嵌套在其他构件类元之中,从而表示定义关系。
构件中定义的类在构件中表示(在构件的"肚子"画类)。
可以用包含构件类元和节点类类元的图来表示编译依赖关系。该关系用带箭头的虚线表示,箭头从用户构件指向它所依赖的服务构件。
若从一个构件指向另一个构件上的接口可以采用虚线表示。
图例的具体示例参见下一章。
1.5. 9实施图(Deployment diagram)
实施图就是部署图,表示构件的实例该使用的,这个在构件图中已经提及。
实施图的语义:
实施图表示运行时过程节点、构件实例及其对象的配置的视图。构件表示代码单元在运行时的表现。运行时不存在的构件不出现在实施图中,而是在构件图表示。
实施图含有用通信链相连的节点实例。节点实例包括运行时的实例,如构件实例和对象构件实例和对象还可以包含对象。模型可以表示实例及其接口之间的依赖关系,还可以表现节点或者其他容器之间实体移动。
实施图表示法:
实施图是节点符号与表示通讯关联的路径构成的网状图,节点符号可以包含构件实例。说明构件存在或运行于该节点上。构件符号可以包含对象,说明对象是构件的一部分。构件之间用虚线箭头相连,说明一个构件使用了另一个构件的服务。必要时可以用构造型说明依赖关系。
实施图类似于对象图,通常用于表示系统中的各个节点的实例。很少有实施图来定义节点的种类和节点之间的关系。
同样,图例的具体形式参见下一章。
该章小结:这一章,我们主要学习了一下UML中的简单的概念,这些概念都是建立在类元的基础上的。类元及类元的关系(图的)表示即组成UML图,我们采用一些工具可以将这些已经产生的UML图转化为简单的程序源码,在下面的章节中我们将分析UML给我们带来的简便。