UML参考手册
第三部分 参 考 资 料
第13章 术 语 大 全
1. abstract(抽象)
抽象是不能被直接实例化的类、用例、信号、其他类元或其他泛化元素,它也可以用来描述没有实现的操作。反义词:concrete。
见abstract operation,generalizable element。
语义
抽象类是不可被实例化的类,即它可以没有直接实例,这既可能是因为它的描述是不完整的(如缺少一个或多个操作的方法),也可能是因为即使它的描述是完整的它也不想被实例化。抽象类是为了以后说明。抽象类必须有可能含有实例的后代才能使用,一个抽象的叶类是没用的(它可以作为叶在框架中出现,但是最终它必须被说明)。
具体类可以没有任何抽象操作(否则,它必为抽象的),但是抽象类可以有具体操作。具体操作是可以被实现一次并在所有子类中不变地使用的操作。在它们的实现中,具体操作可以只使用声明它们的类所知道的特征(属性和操作)。继承的目的之一即将这些操作在抽象的超类中分解以使得它们可以被所有的子类分享。一个具体操作可以是多态的,即后代类中的方法优先于它,但是它也可以不必是多态的,它可以是一个叶操作。一个所有操作都被实现的类可以是抽象的,但是这一点必须被明确声明。一个有着一个或多个未实现操作的类自然是抽象的。
同样的语义也适用于用例。抽象的用例定义了行为的片断,这个行为自己不能出现,但是它可以通过泛化、包括或扩展关系在具体用例的定义中出现。通过在抽象的用例中分解公用的行为,模型变得更小和易于理解。
类似的关系也存在于其他的类元和泛化元素中。
表示法
抽象类或抽象操作的名字用斜体字表示。关键字abstract可以放置在位于名称下面或后面的特性表中,如Account{abstract}。
见class name。
示例
图13-1表示一个抽象类Account,它有一个抽象操作computeinterest和一个具体操作deposite。两个具体子类已经被声明了。因为子类是具体的,所以它们每一个必须实现操作computeinterest。属性总是具体的。
图13-1 抽象和具体的类
讨论
将一个类建模成抽象的或具体的,其间的差别并不像它第一次出现时那么清晰和基本。它更像有关模型的设计结果而不是继承特性。在设计的发展过程中,类的状态可能发生变化。如果将列举出所有可能性的子类加入到具体类中,那么这个具体类可以建模成抽象的。如果子类之间的差别被认为是不必要的且被删除,或者这些差别用属性值而不是用不同的子类表示,那么这个抽象类可以建模成具体的。
简化决定的方法之一是采纳以下设计准则:所有的非叶类必须是抽象的(除了某些为了以后说明的抽象叶类外,所有的叶类必须是具体的)。这并不是UML的规则,它既可以被采用也可以不被采用,设计这个"抽象超类"规则的原因是超类上可继承的方法和具体类上的方法经常有不同的需求,这种需求并不能被单个方法很好地实现。超类中的方法被迫做两种事:定义能被所有后代观察到的通用例子和为特定类实现通用例子。但是这两个目标经常发生冲突。相反,一个非抽象的超类能被机械地分离到一个抽象的超类和一个具体的叶子类中。抽象的超类包含被所有子类继承的方法;具体的子类包含所有特定的可实例化类要求的方法。在抽象的超类规则后也允许在保持特定具体类型的变量或参数与保持着超类的任何后代的变量或参数之间存在完全的区别。
图13-2 具体的超类产生的模糊性
在图13-2中,考虑类letter的声明,它并没有遵循抽象超类规则。该类有一个getNextSentence操作,它返回下一个还没有读的句子的明文,还有resetCursor操作,它将鼠标置回开始处。而子类EncryptedLetter表示已经被加密的字母。操作getNextSentence重载被重载因为明文在被返回前必须要解密。操作的实现完全不同的。因为Letter是一个具体超类,所以(非重载)是不可能的。普通Letter类或EncryptedLetter子类中。
抽象超类方法用于辨别抽象类Letter(它既可能是加密的字母,也可能是未加密的字母)并且加入类NonEncrypedLetter以表示具体例子,如图13-3。在这个例子中,getNextSentence是一个被每个子类实现的抽象操作,resetCursor是一个在所有子类中相同的具体操作。这个模型是对称的。
如果遵循抽象类规则,那么抽象类声明能从类层次中自动确定,并且在图中表示它也是多余的。
声明一个抽象叶类一般是没用的,但有一个例外:当抽象类作为一组全局类作用域的属性和操作的通用命名空间时可以被声明。这种情况较少,大部分用于处理非面向对象语言的编程时,建议用户在大多数情况下不要用它。全局值通过引用全局依赖关系违反了面向对象设计的精神。单实例类可以以更扩展的方式提供同样的功能(可参见[Gamma-95])。
图13-3 抽象超类避免模糊性
2. abstract class(抽象类)
抽象类是可能不会被实例化的类。
见abstract。
语义
抽象类可能没有直接实例,可能有间接实例(通过它的具体后代)。
见 abstract 的讨论。
3. abstract operation(抽象操作)
抽象操作缺少实现,即它只有说明而没有方法。实现必须被具体后代类补充。
见abstract,generalizable,inheritance,polymorphic。
语义
如果一个操作在类中被声明为抽象的,那么该操作缺少在类中的实现,且类本身也必须是抽象的。操作的实现必须由具体的后代来满足。如果类继承了一个操作的实现但是将操作声明是抽象的,那么抽象的声明就使类中被继承的方法无效。如果一个操作在类中被声明是具体的,那么类必须满足或继承从祖先那里得到的实现,这个实现可能是一个方法或调用事件。如果操作在类中根本没有被声明,那么类继承从它的祖先那里得到的操作声明和实现。
操作可以作为方法或由调用事件触发的状态机转化而实现。每个类可以声明它自己的方法、操作的调用事件或者继承祖先的定义。
表示法
抽象操作的名称用斜体字表示,如图13-4,而关键字abstract可以放在操作特征标记后的特性表里
。
图13-4讨论
继承概念的最大用途是支持能被每个具体后代类有区别地实现的抽象操作。抽象操作允许调用者在不知道哪个对象的类是目标的情况下使用操作(假设目标对象通过作为一个抽象类的间接实例支持这个操作,并且这个抽象类有抽象操作的声明)。这种多态操作的重要性在于,决定对象种类的职责从调用者转换到了继承机制。不仅调用者不会有写例子声明的麻烦和代价,并且调用者也不必关心抽象类的哪个子类会存在。这意味着附加子类将与新的操作实现一起被加入。因此,抽象操作、多态和继承在不必改变使调普通行为代码的情况下,通过加入新的对象和行为促进系统的升级。这大大减少了系统升级的时间,更重要的是,它降低了偶然的不协调的可能性。
4. abstraction(抽象)
抽象是确认一件事物本质特征的行为,这种行为将这个事物与其他所有事物区分开来。 抽象涉及到通过观察几组事物的本质公共特性来查找它们的共同点。抽象往往涉及到观察者的观点和目的;不同的目的导致同一事情的不同抽象。所用的建模过程都涉及到抽象,通常存在与不同目的的不同层次上。
抽象是一种将不同层次上的同一概念的两种元素联系起来的依赖关系。
见派生、实现、精化、跟踪。
语义
抽象依赖关系是不同抽象层上的两个元素之间的关系,比如在不同模型中、不同准确度上、不同具体性上或不同优化层中的描述。通常,两个描述不会被同时用到。正常情况下,一个元素比另一个更详细一些,客户元素比提供者元素更详细。如果不明确哪个元素更详细,那么两个元素都可以建模成客户。
抽象依赖关系的构造型是跟踪(关键字trace)、精化(关键字refine)、实现(关键字realize)和导出(关键字derive)。
表示法
抽象依赖关系表示成从客户元素指向提供者元素的箭头,并附有关键字《trace》、《refine》或《derive》。实现依赖关系有它自己特殊的表示符号,表示成指向提供者元素的有着封闭三角形的虚线箭头。
元素之间的映射可以作为约束附加在关系上。
标准元素
derive,refine,trace。
5. access(访问)
访问是一种许可依赖关系,允许一个包引用另一个包中的元素。
见friend、import、visibility。
语义
一个包(客户)如果要引用另一个包(提供者)内的元素,那么它必须引入一个包,该包包括客户包到提供者包的《access》或《import》依赖关系上的元素。一个包可以隐含地获得对由包含该包的任何包所引入的包的访问权(即,嵌套包可以看到包含包可以看到的一切)。
包中的元素可以访问包内所有可见的元素。可见性规则可以总结如下:
* 一个包中定义的元素在同一个包中是可见的。
* 如果一个元素在一个包中是可见的,那么它对所有嵌套在这个包中的所有包都是可见的。
* 如果一个包访问或引入另一个包,那么在要被引入或访问的包中定义为公共可见性的元素对引入包都是可见的。
* 如果一个包是另一个包的孩子,那么所有在父包中定义为公共的或受保护的可见性的元素对子包是可见的。
* 访问或引入依赖关系是不能传递的,如果A能看到B,且B能看到C,这并不意味着A能看到C。
结论:除非一个包能够访问它的嵌套包且嵌套包的内容是公共可见的,否则这个包不能看到它自己的嵌套包的内部。
下面是有关可见性的更深一步的规则:
* 如果一个类元的内容,如它的属性和操作以及嵌套类,在类元中具有公共可见性,那么它们在包中是可见的。请注意一个子系统的未组织的内容是由上面提到的包规则指导的,但是任何子系统本身的属性或操作由这条规则指导。
* 如果一个类元的内容有公共的或受保护的可见性,那么它们对后代类元是可见的。
* 一个类元的所有内容对类元内的元素都是可见的,包括类元的方法或状态机中的元素。
一般情况下都会涉及到对等包中的元素。在这种情况下,一个元素能看到它自己包内的所有元素和被它所在包引入的包的具有公共可见性的所有元素。一个类可以看到其他类中的公共特征。一个类也可以看到它的祖先的受保护的特征。
表示法
访问依赖关系用一个从客户包指向提供者包的虚箭头表示。箭头用关键字《access》作为标号。
讨论
图13-5为一个两个包间的对等层访问的例子。包P能够访问包Q,但是包Q不能访问包P。P包中的类K和L能看到包Q中的类M,但是它们看不到私有类N。除了具有公共可见性的类K之外,类M和N看不到包P中的任何类,因为包Q不能访问包P。要想一个类对对等包是可见的,这个类必须具有公共可见性,并且它的包必须被对等包访问或引入。
图13-5 对等访问
图13-6 访问规则
图13-6为一个有关可见性和访问声明的更复杂的例子。元素名字前的符号代表了它的可见性:+代表公共的,#代表是受保护的(只对后代可见),-代表是私有的(对外界是不可见的)。
类A能看到C和E因为它们包含在包Y和X内。
类C和A能够看到D,因为包Y引入了包Z。类A嵌套在包Y中并且能够看到Y能看到的一切。
类A、C和E能看到B,因为它们嵌套在包X中,而X引入包含B的包V,但是,它们看不到F,因为F在它的包V中是有私有可见性。所以,类F在包V外是看不到的。
类E看不到D因为D在包Z中,而Z并没有被包X访问。
类C和E都看不到A。类A在包U中,U没有被任何包访问。
类B和F能够看到类D和E,D和E建立在包含包中。它们也能看到C,C在包Y中,而包Y被包含包访问。虽然F是私有的,但这并不影响它看到其他的类,而其他的类看不到F。
类B和F能够互相看到,因为它们在同一个包中。类F对外面的包中的类是私有的,但对它自己包中的类不是这样。
6. action(动作)
动作是可执行的原子计算,它导致模型状态的改变和返回值。对比:activity。
见entry action,exit action,transition。
语义
动作是一个原子计算,即它不会从外界中断。动作可以附属于状态机中的转换(在两个状态之间或在一个状态中)或交互中的某一步。通常,它是一个的或近似原始的在系统状态的操作,也常常位于一个独立对象的状态上。典型动作有分配属性值、访问属性或链接的值、创建新的对象或链接、简单算法和向别的对象发送信号。动作是行为建立之外的步骤,动作的意思是指"快速"计算,以使得系统的反应时间不会被减少。系统可以同时执行几个动作,但是动作的执行应该是独立的。
动作也可以附属于转换,当转换被激发时动作被执行。它们也可以作为状态的入口动作和出口动作出现。这些动作由进入或离开状态的转换触发。所有动作都是原子的,即它们执行时完全不会被别的动作所干扰。
活动也是一种计算,但是它可以有内部结构并且会被外部事件的转换中断,所以活动只能附属于状态中,不能附属于转换。与动作不同,虽然活动自己也能中断,但是如果外界不中断它,它可以无限期地持续下去。而动作则不能从外部中断并且可以附属于转换或状态的入口或出口,而非状态本身。
结构
动作包括一个目标对象集合、一个对将要被发送的信号或将要被执行的动作的引用(即请求)、一张参量值表和一个可选的用于指明迭代的递归表达式。
* 对象集合。对象集合表达式产生一个对象的集合。在许多情况下,这个集合包含一个独立的固定的对象。有给定参量表的消息拷贝被同时发送到集合中的每个对象,即广播到每个对象。每个目标独立接收和处理消息的单独的实例。如果集合是空的,那么什么都不会发生。
* 请求。指明一个信号或声明一个操作。信号被发送到对象,操作被调用(对于具有返回值的操作,对象集合必须包含一个对象)。
* 参量表。参量列表。当赋值的时候,参量表中的值必须与信号或操作的参数相一致。参量被作为发送或调用的一部分。
* 再发生。一个迭代表达式,说明需要执行多少次动作,并指定迭代变量(可选)。这个表达式也可以描述一个条件动作(即进行一次或不进行)。
几种动作
* 赋值动作。赋值动作将一个对象的属性值设置定为给定值。该动作包含一个对目标对象的表达式、对象属性的名字和一个被分配到对象内属性槽的值的表达式。
* 调用动作。调用动作导致一个对象上操作的发生,即该对象上操作的调用。该动作包含一个消息名、一张参量表达式的表和一个目标对象集合表达式。目标可能是一个对象集合。在这种情况下,调用同时发生并且操作不会有返回值。如果操作有了返回值,那么它必须有一个对象作为目标。
调用动作是同步的。调用者在再次接收控制之前等待被调用操作的完成。如果操作被作为调用事件实现,那么调用者在再次接收控制之前一直等待,直到接收者执行被调用触发的转换。如果操作的执行返回了值,那么调用者在它再次接收到控制时接受了这些值。
* 创建动作。创建动作导致了对象的实例化和初始化(见Creation)。该动作包含一个对类的引用和一个可选的带有参量表的类作用域操作。动作的执行创建了一个类的新的实例,其属性值从计算它们的初始值表达式中得到。如果一个明确的创建动作被给定,那么它将被执行。操作常常会用创建动作的参量值覆盖属性值的初始值。
* 销毁动作。销毁动作导致目标对象的销毁,该动作有一个针对对象的表达式。销毁动作没有其他的参量。执行该动作的结果是销毁对象及到它的所有链接及所有组成部分(见composition)。
* 返回动作。返回动作导致了一个到操作调用者的控制转换。该动作只允许在被调用使用的操作中存在。该动作包含一个可选的返回值表,当调用者接收到控制时该表对调用者是有效的。如果包含的操作被异步使用,那么调用者必须明确地选择返回消息(作为一个信号),否则它将会遗失。
* 发送动作。发送动作创建了一个信号实例并且用通过计算动作中的参量表达式得到的自变量初始化这个信号实例。信号被送到对象集合里的对象,这些对象通过计算动作中的目标表达式而得到。每一个对象接收它自己的信号拷贝。发送者保持它自己的控制线程和收益,且发送信号是异步的。该动作包含信号的名称、一张信号参量表达式表和对目标对象的对象集合表达式。
如果对象集合被遗漏,那么信号被发送到由信号和系统配置决定的一个或多个对象。例如,一个异常被发送到由系统策略决定的包含作用域。
* 终止动作。终止动作引起某种对象的销毁,这个对象拥有包含该动作的状态机,即该动作是一种"自杀"行为。其他对象会对对象的销毁事件做出反应。
* 无解释动作。无解释动作,一种控制构造或其他构造的动作。
表示法
UML没有一种固定的动作语言,它希望建模者使用一种实际的编程语言去编写动作。下面对OCL的改编是为了写动作伪代码,但这并不是标准的一部分。
赋值动作
target:=expression
调用动作
object-set.operation-mane(argrment list,)
创建动作
new class-name(argument list ,)
销毁动作
object.destroy()
返回动作
return expression list ,
发送动作
object-set.signa-name(argument list,)
终止动作
terminate
无解释动作
if (expression) then (action) else (action)
如果需要明确区别调用与发送,关键字call和send可以作为表达式的前缀,它们是可选的。
讨论
UML 规格说明定义了一组动作,同时也说明在实际实现中可以用支持工具加入其他动作。这个决定是为了平衡精确性需求和开发者使用多种具有很广泛语义概念的目标语言的需要。编程语言的执行语义的变体要比数据结构和有用的控制构造集合中的变体多。不考虑理论上的可行性,微小的区别很难在语言中与实际的方式对应上。选择一种编程语言作为动作语言的基础会降低别的语言的作用,且动作的语义会留下一些不完整性和二义性,而这也是我们所不希望的。如果想使语义更准确些,UML必须与一种动作语言相结合。有些批评者说因为UML太自由了,所以它并不精确,但是它的不精确程度不会超过它所选择的动作语言的不精确程度。UML的真正缺陷是它没有实现动作和其他表达式的多语言表示,但是在今天的多语言世界上这几乎是不可能的。
7. action expression(动作表达式)
动作表达式是决定动作或动作顺序的表达式。
讨论
UML并没有说明一个动作表达式的语法结构,由支持工具来负责这项工作。我们希望不同的使用者可以用编程语言、伪符号,甚至自然语言来表示动作。更精确的语义需要详细的设计,这也是许多用户会用到实际编程语言的地方。
8. action sequence(动作顺序)
动作顺序是被连续执行的一组动作,它是动作的一种。
语义
动作顺序是一组动作,这些动作被连续地执行。整个序列被看作是一个原子单元,即它不能被中断。动作顺序即一种动作,故也可以被附加到转换和交互。
表示法
动作顺序用一个字符串表示,包含一系列由分号分隔开的动作。
如果动作用一种特定的编程语言表示,那么动作对声明序列的语法可以被替代使用。
示例
count:=0;reservations.clear();send kiosk.firstScreen()
9. action state(动作状态)
动作状态的用途是执行动作并转换到另一个状态。
见activity state,completion transition。
语义
动作状态的目的是执行一个入口动作,在这之后进行向另一个状态的完成转换。动作状态是原子的,即它不能被外部事件的转换中断。从理论上讲,它表示一个可以在忽略不计的时间内完成并不与同时发生的其他动作相互作用的计算。而实际上,它需要时间来执行,但是时间要比可能发生事件需要的反应时间要短。它不能有由事件触发的转换。动作状态没有子结构、内部转换或内部活动。它是一种哑状态,可用于把状态机组织成逻辑结构。它通常有一个输出的完成转换。如果有监护条件,则可能有多种输出的完成转换,所以也代表了一个分支。
表示法
动作状态并没有特殊的表示符号。它可以表示为具有入口动作的原始状态,也可以表示为一个活动状态。
10. activation(激活)
激活是操作的执行。激活(也叫做控制期)表示一个对象直接地或通过从属操作完成操作的过程。它对执行的持续时间和执行与其调用者之间的控制关系进行建模。在传统的计算机和语言上,激活对应栈帧的值。
见call,sequence diagram。
语义
激活是执行某个操作的实例,它包括这个操作调用其他从属操作的过程(见Call)。其内容包括一个只能访问激活的局部变量的集合、一个方法内的当前位置(或其他行为描述)和一个表示调用内容的激活的引用(当当前激活终止时,它将恢复控制)。没有返回引用的激活必定是在一个主动类对象的状态机上的转换的结果,当转换完成时,状态机简单地等待下一个事件。
请注意这个定义将一个普通过程描述成可以在一个典型的冯·诺依曼机上实现。但是它以一种普通的方式表示即它也适用于分布环境,该环境中没有共享内存并且在不同的内存空间中堆栈包含一张被链接的激活表。
表示法
激活在顺序图中用一个细长的矩形框表示,它的顶端与激活时间对齐而底端与完成时间对齐。被执行的操作根椐不同风格表示成一个附在激活符号旁或在左边空白处的文字标号。进入消息的符号也可表示操作。在这种情况下,激活上的标号可以被忽略。如果控制流是过程性的,那么激活符号的顶部位于用来激发活动的进入消息箭头的头部,而符号的底部位于返回消息箭头的尾部。
如果有多个对象的并发活动,那么每个激活表示一个并发对象的执行。如果对象之间不进行通信,那么并发激活是独立的且它们的相对执行次数是不相关的。
在过程代码中,激活表示一段持续时间,在这段时间中过程或者被初始过程调用的从属过程是活动的。换而言之,所有活动的嵌套过程激活被同时表示。这些同时发生的嵌套激活是传统计算机的计算栈帧。对于激活的对象的第二次调用而之,第二次激活的符号第一次激活符号的右边,这样它们好像叠加了起来。被叠加的调用可以嵌套于任意的深度。调用可以针对同一个操作(即递归调用),也可以针对用一个对象的不同操作。
示例
图13-7表示由调用引起的激活,包括递归调用。
图13-7激活
11. active(活动的/主动的)
活动状态是已经被进入且没有退出的状态,由一个对象保持。
见active class, active object。
语义
当进入一个状态的转换被激发后,这个状态变多活动的;当离开这个状态的转换激发时,这个活动的状态不再活动。如果一个对象有一个控制线程,那么至少有一个状态是活动的(在退化的例子中,类只有唯一的状态,在这种情况下,一个事件的反应总是相同的)。如果一个状态在一个对象的类的状态机中是活动的,那么这个对象被认为是拥有这个状态。
一个对象可以同时拥有多个状态,活动状态的集合叫做活动状态配置。如果一个嵌套的状态是活动的,那么包含它的所有状态都是活动的。如果对象允许并发性,那么多个并发的子状态可以是活动的。每个转换至多影响活动状态配置中的数个状态。转换中没有被影响的活动状态仍然是活动的。
一个组成状态可以是顺序的或是并发的。如果这个状态是顺序的和活动的,那么它的后继子状态的一个是活动的。如果这个状态是并发的和活动的,那么它的后继子状态的每一个都是活动的。即,一个组成状态扩展成一个活动子状态的与或树,在它的每一层,特定的状态是活动的。
构造跨越组成状态边界的转换时必须保持这些并发性约束。一个顺序组成状态中的转换通常有一个源状态和一个目标状态。激发这样一个转换并不改变活动状态的数量。对并发组成状态的每个子区域,并发组成状态的转换通常有一个源状态和一个目标状态。这样一种转换叫做分叉。如果一个或多个作为目的地的区域被忽略,那么每个被忽略的区域的初始状态被隐含当作目的地;如果其中的一个区域缺少初始状态,那么模型是病态的。激发这种转换增加了活动状态的数量。从并发组成状态退出时,则刚好相反。
见state machine,其中包含有关并发状态和复杂转换语义的详细讨论。
示例
图13-8的上部为一个状态机的例子,它既有顺序的又有并发的组成状态。转换被忽略。图的底部表示可以并发活动的状态的不同的配置。在这个例子中,有四种可能的活动状态的配置。只有叶状态是具体的,而更高层次的状态是抽象的,即一个对象可以不属于这些状态也可以不属于它们的嵌套叶状态。举个例子,不属于Q的子状态的对象可不属于状态Q。因为Q是并发的,所以如果Q是活动的,C和D也必须是活动的。每个叶状态对应一个控制线程。在更大的例子中,可能的配置数量成指数增长,而且不可能将它们全部表示出来。
图13-8并发活动状态。
图13-8 并发活动状态
12. active class(主动类)
主动类的实例是主动对象。
见active object。
语义
主动类的实例是主动对象,主动类的构造型是进程和线程。
表示法
主动类用深色边线的框。
举例
图13-9表示一个主动类和其被动类的类图。图13-10表示含有对应于这个模型的主动对象的协作图。
图13-9 主动类和被动类
图13-10主动对象和并发控制间的合作
13. active object(主动对象)
主动对象拥有一个控制线程并且能初始化控制活动,它是一个主动类的实例。
见passive object,process,thread。
语义
一个主动对象不在另一个线程、栈帧或状态机内运行。在整个系统的执行中,它具有独立的控制期。从某种定义来说,它是一个线程。每个主动对象是不同的执行集中点;主动对象不是再进入的,并且如果没有附加对象的创建递归执行是不可能的。
主动对象是传统计算机术语中执行栈帧的基础。一个主动对象的创建初始化了一个新的状态机实例。当状态机执行转换时,一个执行栈帧被建立并且一直存在直到转换动作完成,且对象等待外部引入。因此,一个主动对象不在另一个对象的作用域内运行。它能被另一个对象的动作创建,但是一旦被创建,它就独立存在。创造者可以是一个主动的或被动的对象。主动对象用事件驱动。别的对象对它的操作应该作为调用事件由主动对象实现。
被动对象可以作为动作的一部分由另一个对象创建。它有自己的地址空间。被动对象没有控制线程。它的操作在一个主动对象的栈帧内被调用。然而,它可以有一个状态机来建模,来表示由于对它的操作引起的状态的改变。
一个传统的操作系统进程最好等同于一个主动对象。操作系统线程可以由或不由主动对象实现。
主动-被动的区分基本上是一个设计决定而且不限制对象的语义。主动的或被动的对象都可以有状态机,它们还能交换事件。
表示法
主动对象的协作角色在协作图中用具有重边线的矩形表示。通常,主动对象角色表示成与其内在部分的组合。
主动对象也可以用具有重边线的对象符号表示,名字下有下划线,但是主动对象仅在执行例子中出现,因此不那么普通了。
特性关键字{active}也可以用来表示主动对象。
示例
图13-10表示一个工厂自动化系统的三个主动对象:一个机器人、一个炉子和一个工厂管理者,其中管理者是一个控制对象。三个对象都同时存在和执行。工厂管理者在步骤1初始化一个控制线程,这个线程会分成两个分别由炉子和机器人执行的并发控制线程(A1和B1)。当每一个执行完毕,就合并到工厂管理者的步骤2。每个对象仍然存在且保持状态直到下一个事件到来。
14. active state configuration(活动状态配置)
活动状态配置是在状态机内同时活动的一个状态集合。一个转换的激发可以改变这个集合中的一些状态,而另一些不变。
见active,completion transition,state。
15. activity(活动)
活动是状态机内正在进行的非原子执行。对比:动作。
见completion transition,state。
语义
活动是状态机内子结构的执行,子结构允许中断点的存在。如果一个转换强迫从控制域退出,那么该转换放弃这个活动。活动并不由内部转换的激发终止,因为并没有状态的改变。内部转换的动作会明确地终止它。
活动可以由嵌套状态、子机引用或活动表达式建模。
示例
图13-11为一个警报系统,它说明了动作和活动的区别。当事件detect intrusion发生时,系统激发一个转换。作为转换的一部分,动作call police发生。它是一个动作,所以通常是原子的。当动作被执行时,没有事件会被接受。当动作被执行后,系统进入Sounding状态。当系统处于这个状态时,它执行sound alarm 活动。活动需要时间来完成,中断活动的事件可能会在此时出现。在这种情况下,sound alarm活动自己并不会中断,只要系统处于sounding状态它就会持续下去。当reset事件发生时,转换被激发并将系统返回到monitoring状态。当sounding状态不再是活动的,它的活动sound alarm也被终止了。
图13-11 动作和活动
16. activity diagram(活动图)
activity graph。
17. activity expression(活动表达式)
活动表达式是对非原子计算、活动的文字表达式。这种表达式理论上讲可分为原子部分,但是允许对整个事情进行文字表示会更方便一些。活动表达式的执行可以被使控制状态的无效转换终止。
语义
活动表达式是由某些语言(如编程语言或其他正式语言)表示的有用的过程或算法。它也可以用人类语言来表示。在这种情况下,工具不能执行它,它也不能被检查错误和其他属性。但是在工作的初期阶段,这已足够了。它也可以表示连续的真实世界操作。
表示法
活动表达式用由某种语言(下例为英语)解释的文字表示。
示例
do/invertMatrix 有限但费时
do/computeBestMove(time-limit) 计算直到时间用尽
do/sound siren 连续运行直到结束
18. activity graph(活动图)
活动图是状态机的一个特殊例子,在该状态机中所有的或大部分的状态都是活动状态或动作状态,所有或大部分的转换由源状态中活动的完成所触发。活动图表示一个程序或工作流。活动图是模型中的完整单元。
见state machine
语义
活动图是强调计算过程中顺序的和并发步骤的状态机。工作流是被活动图所建模的过程的例子。活动图通常出现在设计的前期,即在所有实现决定前出现,特别是在对象被指定执行所有活动前。这种图是状态机的特例,在它当中状态代表活动的执行,就像一个计算或真实世界不间断的操作,而转换由操作的完成触发。活动图可以附属于操作和用例的实现。
在活动图中状态主要是活动状态或动作状态。活动状态是某种状态的速记,该状态有内部计算和至少一个输出完成转换,该完成转换由状态内活动的完成来激发。如果转换有监护条件,那么可以有多个输出转换。活动状态不应该有内部转换或基于明确事件的输出转换。对于这种条件要用标准状态。动作状态是原子状态,即它们不会被转换中断。
通常,活动状态用于对这一个过程的某个执行步骤建模。如果模型中的所有状态都是活动状态,那么计算的结果不会依赖于外部事件。如果并发活动不访问同一个对象且并发活动的相对完成时间不影响结果,那么计算过程是确定的。
活动图可以包含普通的等待状态,该状态的退出由事件触发。但是这种使用降低了集中于活动的目的。如果有多个普通状态,则用普通状态模型。
* 动态并发性。具有动态并发性的活动状态表示并发执行多个独立的计算。活动与一个参量表集合同时调用。集合中的每一个成员都是活动的并行调用的参量表。调用是互相独立的,当所有的调用完成时,活动结束并触发它的完成转换。
* 对象流。有时,查看一下操作和作为它的参量值或结果的对象之间的关系是有好处的。一个操作的输入和输出可以表示成一个对象流状态。它是一个状态的构造型,表示在计算过程中特定点的给定对象的存在。为了更精确,输入或输出对象可以在它的类中声明处在指定的状态。例如,"签署合同"操作的输出为"已签署"状态的合同类的对象流状态。该对象流状态可以是其他多个操作的输入。
* 泳道。活动图中的活动可以依照不同的准则划分为几组。每个组代表活动职责的一些有意义的部分,例如,商业组织负责给定工作流的某一步。根据它们的图形表示法特征,每个组被称作泳道。
表示法
活动图是状态机中的一种,但是几种速记表示法也适用于活动图,如:活动状态、分支、合并、泳道、对象流状态、状态类、信号发送和信号接收表示法和延迟事件。
请看一些可选符号的控制图标,它们可用于活动图表示法和。
举例
图13-12表示一个活动的工作流,它用于处理剧院售票处的订单。它包括一个分支和随后的合并,取决于订单是预订的还是个人票。分叉初始化逻辑上同时发生的并发活动。它们的实际执行可以重叠也可以不重叠。并发性由随后一个相对应的结合终止。如果只涉及到一个人,那么并发活动可以以任何次序执行(假定它们不能被同时执行,这是模型所允许的,但在实际中非常困难)。例如,售票处的人员可以先分配座位,再授予赠品,再借记账户;或者他们可以先授予赠品,再分配座位,再借记账户,但是他们只有在分配座位后才能借记账户。
图13-12 活动图
分叉的一个输出部分有一个监护条件,检查预订者是不是会员。这是一个条件线程,它只有在监护条件被满足时才会激发。如果这个线程没有被激发,那么随后相对应的结合的输入部分被认为已完成。如果预订者不是会员,则只有一个线程被激发,它负责配座位和借记账户,但是不会等待结合处的同步。
* 泳道。活动图中的活动可以分成为几个区域,每个区域在图中用虚线分开因此被叫做泳道。泳道是活动图的内容的组织单元。它没有内在的语义,但可以根据建模者的意愿使用。通常,每个泳道代表真实世界组织内的一个组织单元。
示例
图13-13中,活动被泳道分成三个部分,每个部分对应一个不同的资金保管者。虽然在这个例子中每部分都对应于对象,但是UML并不要求这么做,会有明显的类对应于每个部分,而且这些类可能是执行操作以实现已完成模型的每个活动。
图13-13
这张图也表示了对象流符号的使用。对象流对应于一个订单对象经过整个活动的不同状态。例如,符号Order[placed]表示在计算中的位置,一个订单已经被提高到Request Service活动的placed状态,但是还没有被Take order活动使用。当Take order活动完成后,订单进入entered状态,对象流符号将该状态表示在Take order活动的输出上。该例中的所有对象流表示处于声明中不同时刻的同一对象。因为它们表示同一对象,故它们不能同时存在。如图所示,一条连续的控制路径可以从它们中画过。
* 对象流。由动作输入或输出的对象可以表示为对象符号。符号表示处于计算中某一点的对象,在该点对象适合作为输入或作为输出。虚线从活动状态活动输出之一。虚线箭头也可以表示从对象流到对象作为输入的活动状态的输入转换。通常,同一个对象可以作为一个活动的输出和一个或多个后继活动的输入。
当对象流箭头(虚线)满足冗余约束时,控制流箭头(实线)可以被忽略。换而言之,当动作产生的输出是后继动作的输入时,对象流关系包含一个控制约束。
* 状态类。通常,同一个对象被一些改变它的状态的后继活动所控制。为了更加准确,对象可以在图中出现多次,每次出现表示它生命中的不同的状态。为了区分同一个对象的多次出现,每个点的对象的状态可以放在方括弧内并附加在类的名字旁,例如PurchaseOrder[approved]。这个符号也可用于协作图。
见可用于活动图的其他符号的控制图标。
* 延迟事件。有时,当其他活动进行时,有一种事件必须为了晚一些使用而延迟(通常,没有立即处理的事件会被遗失)。延迟事件是放置在内部队列中,直到它被使用或被抛弃的事件。如果在状态或活动中发生延迟事件,该状态或活动将对它们进行说明,其他事件必须被立即处理,否则将被遗失当状态机进入一个新状态时,如果新状态不延迟这些事件,那么所有的延迟事件会发生。如果新状态中的转换由先前状态中的延迟事件触发,那么转换立即激发。如果几个转换隐含地能发生,则它们中的哪一个会激发并不明确,且施行一条规则以选择一个要激发的转换是语义变更点。
如果一个事件在它所延迟的状态中发生,它可能触发一个转换,在这种情况下它不放在队列中。如果这个事件不触发转换,则它被放在队列中。如果活动状态改变,那么队列中的事件可以触发新状态中的转换,但是如果它们在新状态中仍然是延迟的,那么它们仍然在队列中。如果一个事件在组成状态中必须是延迟的,但可以促使一个或多个子状态中的转换,那么使事件不再延迟以触发转换的能力是很有用的。否则,事件将不得不在每个子状态中延迟而不触发转换。请注意如果一个延迟事件与转换的触发器事件相一致,但是不满足监护条件,那么这个事件并不触发转换,也不会从队列中移去。
一个可延迟事件在状态内表示,后面有一个反斜杠和特殊操作defer。如果事件发生且没有触发转换,则它被保存起来,且当对象转换到另一个状态时再次发生。当对象到达一个它不会被延迟的状态时,它必须被接受或被忽略。若没有defer声明则可以取消先前的延迟。defer指示符可以放于一个组成状态上,这样事件就在整个组成状态内延迟。
动作状态是原子的,所以当这些状态活动时隐含地延迟任何此时发生的事件,无需将它们标记为延迟的。发生的事件被延迟直到动作完成,这时事件可以触发转换。
图13-14 延迟事件和控制图标
举例
图13-14表示者咖啡的步骤。在这个例子中,外部对象(coffeepot)没有表示出来,表示出来的仅仅是由人直接执行的活动。打开壶的动作被建模成发送到壶的事件。活动Get Cups发生在打开咖啡壶之后。在得到杯子后,需要等待直到灯熄灭。然而,存在一个问题,如果light goes out 事件在Get Cups活动完成之前发生,那么因为状态机没有准备好处理事件,它将会丢失。为了避开丢失事件的危险,活动状态Get Cups被标识为延迟light goes out事件。如果事件在活动还在执行时发生,事件不再会丢失。相反地,它被保存在队列中直到状态机离开Get Cups状态,这时它被执行且触发了转换。
请注意light goes out 事件不是Get Cups状态的触发,因此当它出现时也不会终止活动的进行。这个事件是Get Cups活动完成后的一个接收状态的触发者。
* 动态并发性。有一个不确定值的动态并发性用活动符号右上角的复合串表示(如图13-15)。这预示着活动的多个拷贝同时出现。动态活动接收参量表的集合,细节必须用文字描述。如果并发性应用于多个活动,则它们必须包含于一个组成活动中,这个组成活动得到多重指示。
图13-15 动态并发性
* 图结构。状态机必须被良好地嵌套,即它被分解成并发的或顺序的子状态。对一个活动图,分支和分叉必须被很好地嵌套。每个分支必须有一个对应的合并,每个分叉必须有一个相对应的结合。这对活动图来说有时不方便。通常情况是采用部分有序图,其中没有直接的循环,但是分叉和合并不必相匹配。这种图不是良好嵌套的,但是它可以经过指定活动到线程和通过当转换跨越边界时引入同步状态两种方法来转变为良好嵌套图。分解不一定是唯一的,但是对部分有序图来说所有的分解将产生相同的可执行语义。所以,对一个简单的部分有序图没有必要表示一个明确的分解或同步状态。对涉及到条件和并发性的更复杂的图,可能需要更明确的分解。
19. activity state(活动状态)
活动状态表示一个具有子结构的纯粹计算的执行,通常为操作或位于其内的声明的调用或真实世界程序的执行。活动状态可以被使转换离开状态的事件从外部中断。活动状态不必自己中断。对于它可以活动多长时间是没有限制的。
见activity,completion transition。
语义
活动状态是具有内部计算和至少一个输出完成转换的状态,当状态中的活动完成时该转换激发(如果有监护条件则可以有几个这种转换)。活动状态不应该有内部转换或基于确切事件的输出转换。对于这种情况要求使用普通状态。活动状态通常用于对算法执行中的一步建模。如果模型中的所有状态都是活动状态且并发的活动不访问同样的值,那么计算是确定的,即使它涉及并发执行。
活动状态可以引用一个子机,通常为另一个活动图。这与将活动状态扩展成一个子机网络的搭贝是等价的。它是状态机的子例程。
活动状态是一个程序的执行过程的状态而不是一个普通对象的状态。
动作状态是原子的活动状态,即当它是活动时不会被转换所中断。它可以被建模成只有一个入口动作的活动状态。
活动状态可以被用于普通的状态机,但是它们更常用于活动图。
离开一个活动状态的转换通常不包括事件触发器。输出转换被状态中活动的完成隐含地触发。转换可以包括监护条件和动作,注意所有有关违反活动的转换的可能条件都要涉及到,否则控制被挂起。如果多个监护条件赋值为真,那么选择不确定的。
对于其他条件使用普通状态。
表示法
活动状态用以下形状表示:上下边是直线,左右边是凸弧(如图13-16)。活动表达式被放在符号内。在图中活动表达式不必是唯一的。
图13-16 活动
讨论
动作状态用于简短的簿记操作而活动状态用于任何有持续时间或复杂性的计算。这意味着动作可以锁定系统所以它必须是短暂的,但是活动可以被中断,所以如果一些紧急的事情发生不要求系统去完成它。UML语义并不阻止长动作,但是代码生成器可以合理地假定动作打算立即完成,同时活动对于其他动作是可中断的。
20. activity view(活动视图)
活动视图是系统的一个方面,它将行为的说明作为由控制流连接的活动进行处理。活动视图包含活动图并且表示在活动图上。它松散地与其他行为视图组织在一起。
见activity graph
21. (参与者)
参与者是直接与系统相互作用的系统、子系统或类的外部实体的抽象概念。参与者参与到用例或一组连贯的用例中以完成整个目标。
见use case。
语义
参与者刻划和抽象了一个外部用户或与系统和类元相互作用的一个相关的用户集合的特征。参与者是具有集中的目的和含义的理想化形式,不一定有确切对应的物理对象。一个物理对象可以兼有多个无关联的目的,所以可以由多个参与者建模。不同的物理对象可以包含同样的目的,故在这个方面可以由同一个参与者建模。用户对象可以是一个人、一个计算机系统、另一个子系统或另外一种对象。例如,一个计算网络系统的参与者可以包括Operator、System Administrator、Database Administrator和普通的User,也可以有非人类参与者,如RemoteClient、MasterSequencer和NetworkPrinter。
每个参与者定义了一个角色集合,当系统的用户与系统相互作用时会采用它们。参与者的一个集合完整描述了外部用户与系统通信的所有途径。当一个系统被实现,参与者也被物理对象实现。一个物理对象如果可以满足多个参与者的角色,那么它就可以实现多个参与者。例如,一个人可以既是一个商店的售货员又是顾客。这些参与者不是本质上相关的,但是它们可以由一个人来实现。当一个系统的设计被施行时,系统内的多个参与者被设计类实现(见realization)。
参与者与系统的不同的交互作用量化为用例,用例是设计系统和它的参与者的连贯的功能块,用来完成对参与者有意义的事情。一个用例可以包括一个或多个参与者,一个参与者到以参与一个或多个用例。最终,参与者由用例和参与者在不同用例中所担任的角色决定。没有参加任何用例的参与者是无意义的。
用例模型刻划了一个实体(如系统、子系统或类)与外部实体相互作用时产生的行为的特征。外部实体是实体的参与者。对于一个系统,参与者可以既由人类用户又由其他系统实现。对于一个子系统或类,外部元素可以是整个系统的参与者,或者参与者可以是系统内的其他元素,如其他子系统或类。
参与者实例通过与用例实例传递消息实例(信号与调用)来与系统通信,在实现层,与实现这个用例的对象传递消息实例。这通过参与者和用例之间的关联来表达。
参与者可以列出它发送和接收的信号集合。参与者也可以有一组它所支持和需要的接口。参与者的接口必须与和它通信的每个用例的接口相容。换而言之,参与者必须接受用例发送的所有信号,而且它不能发送给用例不能接收的信号。参与者的接口限制了参与者如何映射到类上。参与者也可以有一组表示它的状态的属性。
泛化
多个参与者可以有相同点,即它们可以以同样的方式与同一组用例交流。这个相同性用与另一个参与者的泛化关系来表示,它对参与者的共同方面进行建模。后代参与者继承了由祖先参与者拥有的角色和与用例的关系。一个后代参与者的实例常常在祖先希望被用到时使用(替代原理)。一个后代包括它的祖先的属性和操作。
表示法
参与者可以用具有构造型实体《actor》的类符号(矩形)表示。标准的参与者构造型图标是一个小人,参与者的名字在图的下面。参与者可以用间隔来表示它接收到的属性和事件,它也可以用依赖关系表示它发送的事件。这些是普通类元的能力(如图13-17)。
图13-17 参与者符号
22. actual parameter(实际参数)
见argument。
23. aggregate(聚集)
聚集聚全合关联(整体与部分)中整体的类。
24. aggregation(聚集)
聚合是一种关联形式,它指明一个聚集(整体)和组成部分之间的整体与部分的关系。
见composition。
语义
一个二元关联可以声明为是一个聚合-一种整体与部分的关系。关联的一端指出聚集而另一端是无记号的。两端不能同时是聚集(或组成),但是两端可以同时是无记号的,这时它不是聚合。
从聚合关联实例化形成的链遵循若干规则。聚合关系是可转换的,非对称地跨越所有聚合链接,甚至跨越那些来自不同聚合关联的链接。可转换意味着如果从B到A有聚合链接的路径,那么说"B是A的一部分"是有意义的。非对称性意味着在聚合链接的路径中不存在循环。即一个对象不能直接地或间接地作为它自己的一部分。将两条规则合在一起,从所有聚合关联得到的聚合链接组成的图形就形成了一个部分有序图,该图没有循环(树是一个具体而又常见的部分有序的情况)。图13-18给出了一个例子。
图13-18 对象的聚集是无循环的
从对象B到对象A的链接的有向路径表示存在从类B到类A的合集关联的有向路径,但是关联的路径会涉及到循环,在这当中同一个类可以出现多次。一个类到它自己的聚合关联的有向路径是一个递归。
有一种更强的聚合形式叫做组合。组成是有着附加约束的聚集,这些约束是,一个对象只能是一个组成的一部分且组成对象有安置它的每一部分的责任,即对它们的创建和销毁负责。
见composition。
在普通的聚集中,一个部分可以属于多个聚集,而且它可以独立于聚集存在。通常,聚集"需要"部分,即它可被认为是部分的积聚。但是部分可以独立存在,而不必认为仅仅是"部分"。例如,路径可以认为是段的集合。但是段可以独立存在而不管它是不是路径的一部分,并且同一段可以出现在不同路径中。
见accociation及accociation end以了解聚合的更多特性。
表示法
聚合用一端用连接到聚集类的表示的关联线表示(如图13-19)。如果该聚合是一个组,那么菱形是实心的,见后文图13-68。关联的两端不能同时有聚合标识。
聚合类可以有多个部分。聚集类和每个部类之间的关系是分离关联(图13-20)。
如果有两个或多个聚合关联指向同一个聚集类,那么可以通过将聚合端结合成一个独立的部分来将其画成一棵树(如图13-21)。这要求聚合端的所有属性都是一致的,如,它们必须有相同的多重性。将聚合画成一棵树纯粹是表示方达的选择,并没有附加的语义。
图13-19 聚集表示法
图13-20带有多个部分的聚集。
图13-21 同一个类的多重聚合的表示形式
讨论
聚合和关联之间的区别通常是个人喜好的问题而不是语义的区别,聚合聚合关联。聚合表示了这样一种思想:聚集是它的每一部分的总和。实际上,它加入到关联的实际语义是聚集链不能组成循环这一约束。其他约束,如存在依赖性,由多重性而不是聚集标识说明。除了附在聚合上的很少的一部分语义外,其必要性得到了大家的认同(因为各种不同的原因)。可把它作为建模的一帖安慰剂。
几种次要的特性与聚合有关,但是它们不够可靠故不能使它们成为聚合定义的一部分。这些特性包括从聚集到部分的操作的传播(如移动操作)和紧缩内存分配(以便聚集和它的递归部分能够在一次内存交换中高效加载)。一些作者对几种聚合,但是区别相当细微且可能对通用的建模是多余的。
聚合超越特殊关联的特性。可以跨越不同的类组成聚合。聚合对所有没有循环聚合链包括来自不同关联的链。聚合关联(包括组合关联)的实例实施了一条约束,在某种意义上,聚合是关联的一种泛化,在聚合中,约束和一些操作应用于许多特定种类的关联。
组合有更多具体的语义,它们对应于物理包含和所有权的不同观点。当每个部分由一个对象所拥有并且每个部分没有独立于其拥有者的生命期,那么这时用组比较合适。特别是在当拥有者被创造时,所有的部分也必须分配和初始化;当拥有者毁灭时每个部分也不会存活。类的属性有这些特性,虽然它们没有被明确地建模成一种组合,但是仍然可以被认为如此。通过使用组合,可以避免存储器管理的负担、没有指向任何对象的指针的危险以及被遗弃的对象的危险。组合也适用于这样的情况,由于压缩和操作的原因,一组属性已经被分离到一个不同的类中,但是这些属性真正适用于主类。虽然用来实现关联的包容器类也通常是组成部分的候选者,但是它们必须由代码生成器生成且不会显式地被建模。请注意一个组成部分,如包容器类,可以包含对非组成部分的引用或指针,但是被引用的对象不会在引用对象毁灭时也被销毁。