UML参考手册
第三部分 参 考 资 料
第13章 术 语 大 全
251.许可(permission)
许可是一种依赖关系,它授权客户元素可以使用供给元素的内容(受到内容元素的可见性声明的影响)。
语义
许可依赖关系的原型是访问(Access)、友元(Friend)和导入(Import)。没有原型的单纯的许可依赖关系不存在的。访问和导入依赖关系用在包里,而友元依赖关系则被用于类或者操作。
表示法
许可依赖关系显示为一条虚箭头。箭头方向从客户端(获得许可的元素)到提供者(给予许可的元素),箭头上标注有关键字。
标准元素
友元,导入
252.持续对象(Persistent Object)
持续对象就是在产生它的线程停止存在后继续存在的对象。
253.多态性(polymorphic)
多态是指一种操作的实现(方法或者被调用事件所触发的状态机)可能由它的的子类来提供。非多态性的操作是一个叶操作(leaf operation )。
见 抽象操作(Abstract operation)、泛化(generalization)、继承(inheritance)、方法(method)。
语义
如果操作是多态的,那在它的子类里可能会为它提供一种方法(不论在原来的类里是否已经提供了方法)。否则的话,在声明该操作的类里必须为该操作实现一个方法,并且该方法不能在子类里被重载。一个方法如果已经声明或者从祖先那里继承来,那它就是可以使用的。抽象操作必须是多态的 (因为它没有直接的实现)。如果一种操作被声明为叶操作,那它就是非多态的。
如果一种操作在类中被声明为多态的 --也就是说,没有被声明为叶操作--那它可能在子类里被声明为叶操作。这就可以阻止该操作在更下面的子类里被重载。叶操作不可以在它的子类里被声明为多态的。它也不可以被重载。
如果一个方法在一个类里声明了并且在它的子类里进行了重载,UML并没有指定方法结合的规则(参看下面的讨论)。 可以通过使用有标识的值以语言说明的方式来处理机制,比如在方法 的前面,后面或中间声明。在任何情况下,动作--如显式调用继承来的方法--当然依赖于动作语言。
表示法
非多态的操作是通过关键字{leaf}来声明的。否则的话,操作就被认为是多态的。
讨论
抽象操作必须是多态的 。否则,它根本就不能被实现。Bertrand Meyer 把这称为滞后操作(a deferrd operation ),因为它的定义是在一个类里,而它的实现却在它的子类里。这是继承在建模和编程方面一个重要,可能是最重要的应用。使用继承,操作就可以应用于不同类的对象。调用者不需要知道各个对象所属的类。唯一的要求就是所有的这些对象都继承自定义该操作的祖先。而祖先类不需要实现该操作。只需要定义它们的识别标志。调用者甚至不需要知道可能子类的列表。这意味着新的子类可以在以后加近来而不影响它的多态性操作。当新类加入时,调用操作的源代码不需要修改。在初始代码写完之后再加入新类的能力是面向对象技术的重要基础之一。
多态机制的一个更有争议的使用就是用子类里定义一个不同的方法来替换类里已经定义的方法。这经常被当作一种共享形式而被引用,但这是非常危险的。重载不是增加性的,所以原始方法里的一切都必须复制到子方法里,即使只是做一个很小的变化。这种重复是有可能出错的。特别的说,如果原始方法在以后有了改动,并不能保证子方法也被改动。有时子类使用一个完全不同的操作实现,但是很多专家不鼓励这种重载因为它有潜在的危险。通常来讲,方法应该没有重载的完全继承或者滞后实现;在后者的情况下,父类里没有实现,所以就不存在荣誉或者不一致的危险。
为了使子类能够扩展操作的实现而不失去继承的方法,大多数编程语言提供某种形式的方法合并(method combination),既使用继承的方法但同时也允许加入另外的代码。在C++里,继承的方法必须显式的通过类名和操作名来调用,它把类的继承机制严格的建立在代码之上,所以并不是完全的面向对象的方法。在Smalltalk里,方法可以用 Super 来调用操作,使继承来的方法处理这个操作。如果类的层次关系发生了变化,那继承仍然有效,只是可能这时使用的是另一个类的方法。但是,重载方法必须显式的提供对 Super 的调用。错误可能会发生,而且确实在发生,因为编程人员会忘记有改动的时候插入调用。最后,CLOS提供了非常普通和负责的自动方法合并的规则(automatic method combination rules),在一个操作的执行过程里可能会调用几个不同的方法。整个的操作由几个段共同实现而不是被强制成为一个单一的方法。这是非常普通的但是对使用者来说更难于控制。
UML没有强制只使用一种实现方法合并的办法。方法合并是一个语义变体点(semantic variant point。任何实现办法都可以使用。如果编程语言在方法合并方面比较差,那建模工具可以在生成合适的编程语言代码方面提供帮助,也可以在使用了重载方法而没有发现的情况下发出警告。
254.后置条件(Postcondition)
后置条件就是在操作完成时必须为真的约束(constraint)。
语义
后置条件就是一个在操作执行完成时必须为真的布尔表达式。它是一个断言,不是可执行语句。有时可以提前自动检验后置条件,这要取决于表达式的确切形式。操作完成之后检查后条件可能有用,但是这是调试程序的本质。条件应该是真,任何其他情况都是编程错误。一个后置条件就是作用在操作实现上的一个限制。如果它不被满足,那操作的实现就是被错误的。
见 不变式(invariant)、前置条件(precondition)
结构
后置条件被模型化为一个带有原形<postcondition>的约束,附加到操作上。
表示法
后置条件可以被显示在带有关键子 Postcondition 的说明里。该说明附加在受影响的操作上。
举例
图13-145显示了作用在数组排序的操作上的后置条件。数组(a')的新值与初始值(a)有关联。这个例子以结构化的自然语言表示。以更为正式的语言来声明也是可能的。
图13-145 后置条件
255.强类型(Powertype)
强类型就是其实例是给定类的子类的元类(meatclass)。
见 元类(metaclass)
语义
给定类的子类本身可以被看作元类的实例。这样的元类就被称为强类型。例如,类 Tree 可能有子类 Oak, Elm和Willow。当作对象来看,这些子类就是元类TreeSpecies的实例。TreeSpecies就是Tree范围里的强类型。
表示法
强类型被显示为带有原型<powertype>的类。通过标有原型<powertype>的虚箭头,它被连接到一个泛化路径的集合上去(图13-146)。
图13-146
256.前置条件(precondition)
前置条件是在一个操作被调用时必须为真的约束。
语义
前置条件是一个在操作被调用完成时必须为真的布尔表达式。满足这个表达式是调用者的责任。接收者不用去检查它。前置条件不是可执行语句而是断言;它必须为真,它不是执行操作的方式。为保证可靠性,在操作开始时检查前置条件可能有用,但是这是调试程序本身的性质。条件应该是真,任何其他情况都是编程错误。如果这个条件不被满足,那关于操作或系统的统一性不能做任何评价。它可以可靠的发现错误。实际上,接收者显式的检查前置条件可以发现许多错误。
见 不变式(invariant)、后置条件(postcondition)。
结构
后置条件被模型化为一个带有原形<postcondition>的约束,附加到操作上。
表示法
后置条件可以被显示在带有关键子 Postcondition 的说明里。该说明附加在受影响的操作上。
举例
图13-147显示了作用在矩阵乘法操作上的前置条件。
图13-147
257.表示元素(presentation element)
表示元素就是一个或多个建模元素的文本或图形方式的投影。
见 图(diagram)
语义
表示元素(有时也被称为视元素,尽管它们也包含非图形方式的表示)表示了一个模型中为人感知的信息。它们是表示法。它们显示了部分或者全部的关于一个模型元素的语义信息。它们也有可能加入对人有益的美学信息,例如,把概念上相互联系的元素归为一组。但是所加入的信息没有语义内容。人们希望表示元素应该作到在低层的模型元素发生变动时能够保持自身的正确。这样,模型元素不用为保证操作正确而担心表示元素。
该书中,UML 表示法的描述定义了从模型元素到屏幕上的图形表示的映射。表示元素作为对象的实现是工具实现的责任。
258.简单类型(primitive type)
简单类型就是一个事先定义好了的基本数据类型,比如整数或者字符串。
见 枚举(enumeration)
语义
简单类型的实例是没有标记。如果两个实例具有相同的表示法,那它们是无法区分的,可以通过值来传递而不会丢失任何信息。
简单类型包括数字和字符串,也可能是别的系统所依赖的数据类型,例如,日期和货币,它们的语义在UML以外已经事先定义好。
人们希望简单类型能够和编程语言里的类型紧密对应。
见 枚举(enumeration),它是用户自定义的数据类型而不是事先定义的简单类型。
259.私有性(private)
私有性是一个可见性值,它表明给定元素在它的名称空间以外是不可见的,即使是对该名称空间的后代也是不可见的。
260.私有继承(private inheritance)
私有继承是一种结构的继承,这种继承没有继承行为声明。
见 实现继承(implementation inheritance)、接口继承(enterface inheritance)、可替代性规则(substitutability)
语义
泛化(generalization)可能具有原型《implementation》.这就表明客户元素(通常是一个类)继承了提供者元素的结构(属性,关联和操作),但是没有必要使它自己的客户元素也能使用该结构。因为这样一个类(或者别的元素)的祖先对别的类是不可见的,这个类的实例不能作为变量使用,也不能作为提供者的类的参数使用。也就是说,类不能替代它私有继承的提供者。私有继承是不遵守替代性规则的。
表示法
在泛化箭头上标注关键字 Implementation 来表示私有继承,其中的箭头方向应该从继承元素(客户)到提供被继承结构的元素(提供者)。
讨论
私有继承只是一种实现,它不应该被认为是对泛化的一种应用。泛化要求可替代性。在不包含实现结构的分析模型里,私有继承不是非常有意义。即使对于实现,因为私有继承包含了对继承的非语义性质的应用,所以,对私有继承的使用也应该多加小心。通常,更为明智的替代方式就是使用与提供者类之间的关联。许多作者认为私有继承根本就不能使用,因为它以非语义方式使用继承,而这种方式在模型发生变动时是非常危险的。
261.过程表达式(procedure expression)
过程表达式就是其计算代表了一个过程的执行的表达式,而这个过程会影响到正在运行的系统的状态。
语义
过程表达式是一个可执行算法的编码。它的执行可能(实际上经常是)影响系统的状态--也就是说,他的执行具有副作用。通常,过程表达式并不返回值。它的执行目的就是改变系统的状态。
262.进程(Process)
1. 操作系统里的一个重量级(heavyweight)的并发和执行的单元。 参看 线程;线程包括重量进程和轻量进程。如果需要,可以通过使用构造型产生实现上的区别。
2. 一个软件开发过程--开发一个系统的步骤和指导。
3. 执行一个算法或者是动态处理一些事情。
263.产品(product)
产品就是开发的制品,比如 模型、代码、文档、工作计划。
264.投影(projection)
投影就是从一个集合到该集合的子集的映射。大多数的模型和图都是从可能得到的所有信息的集合上产生的投影。
265.特性(Property)
特性就是表示传递有关模型元素信息的值的一般性术语。属性具有语义效果。在UML中一部分属性已经事先定义好了;其他的特性是用户定义的。
见属性(attribute)、关系(relation)、带标签的值(tagged value)
语义
特性不但具有有带标签的值(用户定义的)和附加在元素上的关系(用户定义的),而且具有内建的标志(由UML定义的)。从一个用户的观点来看,属性是内嵌的还是作为带标签的值由用户实现的,这一点往往并不重要。
讨论
应该注意到我们是在非常普遍的意义上使用特性的,代表附加在元素上的任何可能值,包括属性(attribute),关联和带标签的值。在这种意义上,特性可能包括可以在一个给定元素开始找到的间接得到的值。
266.特性列表(property list)
特性列表就是一个文本语法,其目的是显示附加到元素上的特性或特性组成,特别是带标签的值,还包括模型元素的内建的特性。
表示法
包含在括弧内的一个或者多个由逗号分隔的特性声明。每个特性声明具有下面的形式:
特性名 = 值
或
特性直接量
这里的特性直接量是一个唯一的枚举值,它的出现代表了一个唯一的特性名称。
举例
{abstract , author = Joe , visibility = private }
表示可选项
如果特性声明经过合适的标注后能够和其他的信息区分开来,有的工具可能就会在单独的行上表示特性声明,带有括弧或者不待括弧。例如,类的特性可以以一种不同的显示方式,例如斜体或者不同的字体,列在类的名字下面。这是工具的问题。
注意:特性串可能用于表示内嵌属性和代标签的值,但这种使用在规范形式简单时应避免。
267.受保护性(Protected)
受保护性是一个可见性值,它表示给定的元素在它自己的名称空间外只对它的后代的名称空间可见。
268.伪属性(Pseudoattribute)
与行为类似于属性的类相联系的值;也就是说,它的每个实例都有唯一的值。
见 鉴别器(discriminator)、角色名(rolename)
语义
伪属性 包含关联角色名称和泛化鉴别器。关联角色名称是在类里关联的另一端的伪属性。泛化鉴别器是在父元素里的伪属性。在每个子元素里,鉴别器的值就是子元素的名字。
伪属性可以被作为一个名字用在表达式里以便从对象里取得一个值。因为属性名称和伪属性名称都可以用在表达式里,它们处在相同的名称空间里所以必须在这个名称空间里保证唯一。对于继承来的属性,它们的名称也必须和伪属性名称保证唯一。
269.伪状态(pseudostate)
伪状态是在一个状态机中具有状态的形式而其行为却不同于完整状态的顶点。
参看 历史状态(history state)、初始状态(initial state)、连接状态(junction state)、桩状态(stub state)
语义
当一个伪状态处于活动的时候,状态机还没有完成它的运行到达结束,也不会处理事件。伪状态包括初始状态,连接状态,桩状态,历史状态。伪状态用来连接转换段,到一个伪状态的转换意味着会有一个到另一个状态的自动转换而不需要事件触发。
终结状态和同步状态不是伪状态。它们是特殊的状态,可以在状态机完成运行到达结束时仍然保持活动,但是从它们出发的转换存在限制。
270.公有(Public)
公有是表示给定元素在它的名称空间以外仍然可见的属性值。
271.限定词(qualifier)
限定词就是二进制关联上的属性或者属性组成的列表的插槽(slot),而在这个关联中,属性的值从整个对象集合里选择一个唯一的关联对象或者关联对象的集合。限定词是一个关联的遍历索引。
见 关联类(association class)、关联末端(association end)
语义
一个二进制关联把一个对象映射到一个关联对象的集合上。有时通过提供一个能够区分集合里其他对象的值,就可以把一个对象从集合里选取出来。这个值可以是目标类的一个属性。但是,通常来说,这个值可能是关联本身的一部分,即一个其值是在增加新的连接到关联类的时候有产生者提供的关联属性。这样一个在二进制关联上的属性就叫做限定词。一个对象连同一个限定值决定一个唯一的关联对象或者对象的子集(有点少见)。该值限定了关联。在一个实现语境中,这样的属性被叫做索引值。
限定词被用来从有关联联系到一个对象(被叫做 被限定对象)上的对象集合中选择一个或者多个对象(图13-148)。
被限定值选中的对象称为目标对象。限定词总是作用于存在很多目标方向的关联。在最简单的情况下,每个限定词只从目标关联对象集合中选择一个对象。也就是说,一个被限定对象和一个限定值产生一个唯一的关联对象。给定一个被限定对象,每个限定值映射到一个唯一的目标对象。很多种类的名称都是限定词。在一定的语境中这样的一个名称映射成一个唯一的值。被限定对象提供语境,限定词是名字,而目标对象就是结果。任何ID或者别的唯一代码都是限定词;目的是为了唯一的选定一个值。数组可以被设计成一个被限制的关联。数组是被限定对象,数组下标就是限定词,而数组元素就是目标对象。对于一个数组,限定词的类型是整数范围。
限定词可以被用在一个导航表达式里以选择通过关联联系的对象的一个子集--也就是说,那些具有限定词属性值或者值的列表的关联。限定词在通过关联联系的对象的集合中充当选择器。在大多数情况下,限定词的目的是为了从相联系的对象结合中选择出一唯一的对象,这样一个被限定关联行为就象是一个查询表。
结构
限定词。 一个限定词属性是二进制关联末端的一个可选部分。限定词限定了附加在关联上的类。一个类的对象和一个限定值从二进制关联的另一端的类中选择一个对象或者对象的集合。二进制关联的两端都有限定词是可能的,但是这种情况是很少见的。
限定词是一个关联属性或者属性的列表。每个属性具有一个名字和一个类型但是没有初始值,因为限定词不是独立的对象,当向关联中增加连接时,每个限定值都必须显式声明。
限定词不能用在n元关联中。
多重性。 被限定关系的多重性被放在二进制关联限定词的相对的一端(应该记住被限定的类和限定词一同形成一个和目标类相联系的复合值)。也就是说,限定词附加在关联的近端(neat end),而多重性和角色名称附加在远端(far end)。附加在目标关联末端的多重性表示通过一个组成(源对象,限定值)可以选择多少个对象。普通的多重性值包括0..1(可以选择一个唯一的值 ,但是每个可能的限定值不一定选择一个值),1(每个可能的限定值选择一个唯一的目标对象,所以限定值的取值域必须是有限的。),和*(限定值是把目标对象划分为各个部分的索引)。
在大多数情况下,是0或者1。这种选择意味者一个对象和一个限定词最多可能产生一个关联对象。为1的多重性意味者每个可能的限定值恰好产生一个对象。者显然要求限定词的取值域必须是有限的(至少在计算机实现范围内)。多重性在映射有限的枚举类型方面是有用的--例如,由PrimaryColor (是red, green和blue 的枚举)限定的 Pixel 可以为图象中的每个点产生由 red-green- blue 值组成的三位字节。
未限定关联的多重性没有显示说明。但一般假定它们的多重性是很多,至少是多于一个。否则,就没有需要限定词。
被限定关联上的方向很多的多重性没有很重要的语义影响,因为限定词没有减少目标集合的多重性。这样的多重性代表了一个设计要求:必须提供一个索引以遍历这个关联。那样的话,限定词把目标对象集合分成各个子集。在语义上,除了有一个关联属性外没有增加任何东西,这个关联属性也划分连接(隐式的)。在一个设计模型中,限定词的设计必须作到遍历是高效的--也就是说,不能要求在目标值中做线性搜索。通常通过某种类型的查询表来实现。在数据库或者数据结构中的索引被较为合适的作为限定词。
在被限定对象的相反方向(也就是说,从目标类到被限定对象),多重性代表可以联系目标对象(被限定对象,限定词)的组成数目,而不是被限定对象的数目。也就是说,如果几个组成(被限定对象,限定词)映射到相同的目标对象,那反向多重性是 很多(many)。值为1的从目标到限定词的反向多重性意味着只有一个被限定对象和限定值的组成同目标对象向联系。
表示法
限定词被显示为附加在关联路径末端的一个小矩形,其中的路径介于最终路径段和限定类的符号之间。限定词的矩形是路径 的一部分,而不是类的一部分。限定词附加在它所限定的类上 --也就是说,被限定类的一个对象连同一个限定值在关联的另一端选择一个目标类的集合 。
限定词属性列在限定词的框中。列表中可能有一个或者多个属性。限定词属性 和类属性具有相同的表示法,除了初始值表达式是没有意义的以外。
表示可选项
限定词不可能被隐藏(它提供了重要的细节,忽略这些细节会改变关系的内在性质)。
某种工具可能会使用更细的线来表示限定词的矩形以便清楚的和类的矩形区分。
限定词的矩形最好是比它所附加的类的矩形要小,虽然实际情况并不总是这样。
讨论
被限定关联上的多重性处理起来就象 被限定对象和限定词是一个单一的实体,一个复合键。在正向方向中,目标对象末端的多重性代表了与复合值(被限定对象+限定值)相联系的对象的数目。在反向方向中,多重性刻画了与目标对象联系的复合值的数目,而不是与各个目标对象联系的被限定对象的数目。这就是为什末限定词被放在关联路径中靠近类符号的那一端的原因--你可以认为关联路径把复合值连到目标类上。
不能预先给未限定关系声明多重性。但是实际上,在正向方向上,它的多重性往往是很多(many)。除非很多目标对象被联系到一个被限定对象上,否则限定一个关联就是毫无意义的。对于逻辑建模,限定词的目标就是通过添加限定词把多重性减为1,这样可以保证查询时,只返回一个单一的值,而不是一个值的集合。限定值的唯一性通常是一个重要的语义条件。几乎所有的应用都有很多被限定关联。很多名称是限定词。如果一个名称在一个特定的语境中是唯一的,那它就是一个限定词,而且这个语境应该被正确的识别和建模。并不是所有的名称都是限定词。例如,人名就不是限定词。因为人名是含义模糊的,大多数数据处理应用使用某种识别数码,例如客户数码,社会保险号码,或者是雇员号码。如果应用要求查询信息或者在查询键的基础上遍历数据,通常模型应该使用被限制关联。任何语境,如果其中的名称和识别代码被定义为从集合中作出选择,她们通常应该定义为被限定关联。
应该注意到,限定值是一个连接 的属性,不是目标对象。考虑一下Unix 文件系统,每个目录都是一系列入口,这些入口在这个目录中是唯一的,尽管相同的名字可以在别的目录中使用。每个入口指向一个文件,这个文件可以是数据文件或者是另一个目录。可以有多个入口指向相同的文件。如果是这样的话,这个文件就有多个别名。Unix系统被设计成多对一的关联,在这个关联中,文件名所限定的目录产生一个文件。因该注意到文件名不是文件的一部分。文件并不是只有一个名字。可能在很多目录中有很多名字(甚至是在相同的目录中有几个名称)。名字不是文件的属性。
限定关联的主要动机就是满足设计具有自然而且重要的实现数据结构的重要语义状态的需要。在正向方向中,限定关联是一个查询表--对于一个限定对象,每个限定值产生一个目标对象(或者是空值如果在值集合中没有对应的限定值的话)。查询表通过数据结构来实现,如 Hash 表,B-树,比必须线性查询的无序列表有效的多的有序列表。在几乎所有的情况中,使用链结表或者别的无序数据结构来查询名称或者代码,这种设计是不好的,尽管很多程序员这样做。使用限定关联来为合适的状态建模 及 使用有效的数据结构来实现它们,对于一种好的编程是很重要的。
对于一个逻辑模型,在正向方向上使用多向多重性没有太多意义,因为实际上限定词并不能增加任何关联属性无法显示的语义信息。但是在为算法和数据结构设计准备的模型中,限定词带有另外的含义--即使选择更为有效。换句话来说,被限定关联表示了为查询在限定值上作了优化的带有索引的数据结构。在这种情况下,如果存在一些必须通过一个普通的索引值访问而不需要查询任何别的值的值,多向多重性就可以表示这些值集合。
通常限定词属性不应该包含在目标类的属性中,因为它在关联中的出现是多余 的。但是在索引值的情况下,可能有必要选择一个本身就是目标类的属性的值,把它作为冗余的限定词。索引值固有地就是冗余的。
约束
有些复杂的状态并不能用不存在冗余的联系来直接建模。最好的方法是通过被限定关联加上显式说明的附加约束来捕捉基本的访问路径。因为这种状态并不普遍,所以我们认为试图找到一个能够直接捕捉所有多重性约束的表示法所带来的好处不如这样做所带来复杂性多。
图13-149 简单限定词图 13-150 在一个目录下具有多个名字的文件例如,让我们来考虑其中的文件名唯一 的确定一个文件的目录。一个文件可能对应多个 目录-文件名 组成。这就是我们在前面所看到的模型。图13-149 显示了这个模型。
现在,我们想加入附加的约束。假定一个文件必须仅在一个目录里,但是在哪个目录里,它可以有很多个名字--也就是说,有多种方法来命名相同的文件。我们可以为此在File 和 Directory 之间建立一个冗余的关联,在 Directory 上存在一个单向的多重性。(图13-150)。 这两个关联的冗余通过约束{ same }来表示,约束{same} 表示这两个元素是相同的但是在不同的细节级别。因为这些关联是冗余的,所以只有被限定关联才能实现;而其他的关联则被作为作用在它的元素上的运行时约束。
为人所熟悉的约束就是每个文件可能出现在多个目录里,但是不论它在何处出现,它都必须具有相同的名字。别的文件可能具有相同的名字,但是它们必须出现在不同的目录里。这可以通过把 Filename 作为 File 的一个属性而同时又把类属性和限定词约束为相同(图13-151)。这种模式经常作为一个搜索索引发生,尽管在一个普通的索引中,被限定目标的多重性是多向的。所以,这种情况比索引具有更多的语义含义,因为索引只是一个实现工具。
图13-151 在所有目录里具有相同名字的文件图 13-152 在任何目录里最多只有一个名字的文件第三种情况是允许一个文件以不同的名字出现在多个目录里,但是这个文件在单个目录里只能出现一次。对于这种情况,我们可以使用冗余被限定关联和共享相同属性filename的关联类来建模。(图13-152)
这些例子连同冗余关系说明了约束的本质。实际上,较为令人满意的做法是以文本的方式声明约束,而以图形的方式来声明被限定关联。
272.查询(query)
查询是返回一个值但是不会改变系统状态的一种操作;是没有副作用的操作。
273.实施(realization)
实施就是声明和它的实现之间的一个联系。它表示不继承结构而只继承行为。
见 接口(Interface)
语义
声明刻画了某种事物的行为和结构,但是不决定这些行为如何实现。而实现则提供了如何以高效可计算的方式来实现这些行为的细节。声明行为的元素和实现行为的元素之间的联系叫做实施。通常有很多方式来实施一个声明。一个元素可以实施多个声明。所以实施是元素之间多对多的联系。
实施的含义就是客户 元素必须支持服务元素的所有行为,但是没有必要和它的结构或者实现相匹配。例如,一个客户类元(classifier)必须支持服务类元的所有操作,同时它也必须支持所有声明服务类元的外部行为的状态机。但是任何服务类元的说明实现的属性,关联,方法 或者状态机 都不和客户类元发生联系。应该注意到客户实际上并不从服务器那里继承操作。客户必须自己声明或者从祖先那里继承这些操作以覆盖服务器的所有操作。也就是说,实施中的服务器表明客户必须有哪些操作,但是由客户提供它们的操作。
某些种类 的元素,例如接口和用例,是用来声明行为的,它们不包含任何的实现信息。别的元素,例如类,是用来实现行为的。它们包含实现信息,但是也可以以一种更为抽象的方式把它们作为声明符来使用。通常,实施把声明元素,比如用例或者接口,联系到实现元素上,例如合作或者类。也可能使用实现元素,比如类,来做声明,可以把它放在实施中声明的一边。在这种情况下,只有服务器的声明部分影响客户。实现部分和实施联系是不相关的。更为精确的说,实施就是两个元素之间的联系,而在这个联系中,其中一个元素的外部行为声明部分影响另外一个元素的实现部分。这也可以认为是只继承行为声明而不继承结构或者实现(由客户声明操作的需要)。
如果声明元素是一个抽象类,没有属性,没有关联,只有抽象操作,那这个抽象类的任何声明实施这个抽象类,因为除了声明外没有其他可以继承。实现元素必须支持声明元素的所有行为。例如,一个类必须包含它所实施的接口的所有操作和语义,这些语义和接口要求的所有声明相符。类可以实现额外的操作,而操作的实现也可以做额外的事情,只要没有违反接口操作的声明。
表示法
实现关系用一个虚线路径来表示,其中在靠近提供声明的元素的一端带有闭合三角形箭头,路径的末端在提供实现的元素一边。(图13-153)
讨论
另外一种重要情况是通过合作实施用例(图13-154)。
一个用例声明了外部可见的功能和行为序列,但是它没有提供实现。合作描述实现用例行为的对象以及这些对象为了实现这些行为而相互作用的方式。通常,一个合作实现一个用例,但是合作可以用附属的合作来实现,每个附属合作完成一部分工作。实现一个合作的对象和类通常也出现在别 的合作中。合作中的每个类向被实现的用例贡献一部分功能。所以,一个用例最终是由几个类通过片(slices)来实现的。274.实施(realize)
实施就是为声明元素提供实现。
见实现(realization )
275.接收(receive 动词)
接收就是处理从发送者传送过来的消息实例。
见 发送者(sender)、接收者(receiver)
276.接收者(receiver)
接收者就是处理从发送者传送来的消息实例的对象。
277.接收(reception)
类元准备对信号接收作出反应的声明。它是类元的一个成员。
语义
接收就是类元准备接收信号实例的声明。接收类似于一个操作。接收声明了类元支持的消息的特征,并且说明了它的含义。
结构
一个接收具有下面的属性:
多态性 类元对信号的响应是否总是相同的。由属性IsPolymorphic 使用下面的值来编码;
true 响应是多态的:响应依赖状态,并且可以被后代重载。
False 不论处于何种状态,响应必须是相同的,而且不能被后代重载。净效果就是在处理这个事件的状态机上必须存在一个转换。
信号 指定类元准备响应的信号。
声明 一个说明对该信号的接收带来的效果的表达式。
表示法
一个接收可以显示在类或者接口的操作列表里,使用操作的语法,在信号的名字前面加上关键字 signal .
另外的方法,信号特征的列表可能被放在它自己的分隔块里;它的分隔块具有名字 Signal. 这两种方式都显示在
图13-155
278.引用( reference )
引用是一个模型元素的代表;通常被称为指针。
语义
模型元素通过两种元关系相联系:拥有(Ownership)和引用。拥有关系是在一个元素和它的组成部分,在它里面定义的部分,以及它所拥有的部分之间的关系。拥有关系形成一棵严格的树。被包含的元素附属于包含元素。拥有关系,设置控制,模型的存储都是建立在包含层次上。
引用是相同细节层次上的元素之间的关系,或者是处于不同包容体的元素之间的关系。例如,引用是一个关联和它的参与类之间的关系,是一个属性和它的类或者数据类型之间的关系,是一个范围模板和它的参数值之间的关系。为了是引用成为可能,执行引用的元素必须对被引用元素是可见的。通常这意味着包含引用源的包必须对包含引用目标的包具有可见性。这就要求在包之间应该存在合适的访问或者导入关系。这还要求被引用元素必须具有一个可见性设置,以允许它在它自己的包之外是可见的,除非引用源在同一个包里。
注意引用是一个内部元模型关系,不是用户可见的关系;它被用来创建别的关系。
279.细化(refine)
在表示法中说明细化依赖关系的关键字。
280.细化(refinement)
细化就是代表对已经在一定细节水平或者在一个不同的语义水平上做了声明的事物做更为全面的声明的关系。
见 抽象(abstraction)
语义
细化就是在具有映射关系(不必要是完整的)的两个元素之间的一个历史或者可计算的连接。通常,这两个元素处在不同的模型。例如,一个设计类可能是一个分析类的细化;它可能具有相同的属性,但是它们的类可能来自一个特定的类库。但是,一个元素也可能细化一个相同模型中的元素。例如,一个经过优化的类是简单但不是非常有效的类的细化。细化关系可能包含映射关系的描述,这个映射可能使用一种正规语言写的(例如 OCL 或者 一种编程语言 或者 逻辑语言)。也可能是非正式的文本(显然,它排除了任何自动计算但是可能对初期的开发有用)。细化可以用来为阶段性开发,优化,转变和框架细化建立模型。
结构
细化是一种依赖关系。它把一个客户(更为发展的元素)联系到一个服务器(作为细化基础的元素)上。
图13-156 细化
表示法
细化用带有关键字 refine 的依赖性箭头(一个头在一般性元素,尾在特殊元素上的虚线箭头)来表示。映射关系可以通过连到一个标记上的虚线附加到依赖性路径上。已经提出了各种各样的细化,可以用更进一步的原型来表示。在很多情况下,细化连接处在不同模型中的元素所以在图形方式下是不可见的。通常它是隐式的。
举例
优化是一种典型的细化。图13-156 显示了一个棋盘,它在分析模型里具有简单的表示,但是在设计模型里有更为精细和模糊的表示。设计类不是分析类的特化,因为设计类具有一个完全不同的形式。分析模型里的类和设计模型里的类具有相同的名称,因为它们代表不同语义水平上的同一个概念。
281.具体化(reification)
具体化某事物的动作。
见 具体化(reify)
282.具体化 (reify)
把通常不被当作对象的事物当作对象处理。
讨论
具体化具有长久的哲学和文学含义。 它把抽象概念的性质作为神话和诗歌里的事物和人来进行描述。例如,神 Thor 这种称呼就是对雷的具体化。柏拉图的理想主义理论使人们普遍流行的观念得到了改变。他把纯粹的概念,比如美,善,勇气等,认为是真正永恒的现实,而把物理实体作为不够完美的复制品--具体化达到了它的最终极限。
具体化是面向对象中最重要的观点之一,它几乎在建模的每个方面都起着基础的作用。建立一个模型首先要求把对象放到连续的现实世界中去。人很自然的以他们说话的顺序做这件事--一个名词是一个事物的具体化,而一个动词是对一个动作的具体化。在建模和开始没有作为对象处理的程序,比如动态行为中,具体化是特别有用处的。大多数人把操作当作是一个对象,但是那操作的执行(这个词本身就是一个具体化)呢?通常人们把执行当作是一个过程。具体化并给它一个名称--把它叫做激活--你立刻就可以赋属性给它,形成同别的对象的联系,操纵它,或者存储它。行为的具体化把动态的过程转化为可以存储和操纵的数据结构。这对于建模和编程是非常有力的概念。
283.联系(relationship)
联系就是模型元素之间具体化的语义连接。各种联系包括关联,泛化,元联系,流(flow)以及几种在依赖关系下分组的联系。
语义
表13-2 显示了各种UML的联系。第一列(类型)显示了它们在元模型中如何安排的分组准则。第二列(变种)显示了不同种类的联系。第三列(表示法)显示了各种联系的基本表示法:关联是实心路径,依赖是虚箭头,泛化是带有三角形箭头的实心路径。第四列(关键字)显示了关键字和附加的语法。
表13-2 :UML联系
284.仓库(repository)
仓库是模型,接口和 实现的存储地,是操纵开发制品的环境的一个组成部分。
285.请求(request)
请求是发送给实例的激励的声明。它可以是一个操作的调用或者是一个信号的发送。
286.要求(requirement)
要求就是期望的系统的性质、特征或者行为。
语义
文本方式的要求可以构造为带有构造型<requirement>的注释。
讨论
术语requirement(要求)是一个自然语言词汇,它对应了意图说明系统的期望特征的各种UML结构。很普遍的,对应于用户可见的交易的要求被作为用例捕获。非函数化的要求,例如,执行和质量韵律,可能被捕获为文本声明,他们最后跟踪到最终设计的元素。UML注释和约束可以被用来代表非函数化的要求。
287.职责(responsibility)
职责是类或者别的元素的契约或义务。
语义
职责可以表示为注释上的一个构造型。注释附加在具有这个职责的类或者别的元素上。职责表达成一个文本字符串。
表示法
职责可以表示为在类元符号矩形里的命名区域。(图13-157)
288.重用( reuse )
重用就是对已经存在的制品的使用。
289.角色(role)
角色就是位于一个对象结构里的已命名插槽,该结构代表处在特定语境的元素的行为。一个角色可以是静态的(比如一个关联端点)也可以是动态的(比如合作角色)。合作角色包括类元角色和关联角色。
见 合作(collaboration)
290.角色名称(rolename)
角色名称就是关联里的特定关联端点的名称。
见伪属性(pseudoattribute)
语义
角色名称提供了一个名称,不但用于在使用关联的对象之间导航,而且还被用来在关联里区分关联端点。因为角色名称可以在这两种互补的方式下使用,所以角色名称必须在两个名称空间里同步的保持唯一。
一个关联里的角色名称必须是不同的。在一个自关联(不止一次包含相同的类的关联)里,角色名称必须消除附加在相同类上的端点之间的歧义。在其他的情况下,角色名称就是可选的,因为类名可以用来消除端点之间的歧义。
一个角色也被用来从一个对象导航到相邻的相关对象。每个类都能看到附加在它上面的关联,而且可以用他们来找到联系到它的实例上的对象。习惯上,位于附加在相邻类上的关联端点之上的角色名称被用来形成一个导航表达式,以访问有这个关联联系的对象或对象的集合。在图13-158里,
类 B 通过一个单对多的关联关联到A上,通过一个单对单的关联关联到类C上。给定类B的一个实例bb,表达式bb.theA产生类A的对象的一个集合,而表达式bb.theC产生类C的一个对象。实际上,位于关联远边的角色名称就象是类的一个伪属性--也就是说,它可以在访问表达式里作为一个术语使用以遍历整个关联。因为角色名称可以象属性名称一样被用来提取值,所以角色名称进入处在关联远端的类的名称空间。它和属性名称具有相同的名称空间。在名称空间里,角色名称和属性名称都必须是唯一的。属性和关联角色名称被继承后,属性名称和伪属性名称在被继承的名称里必须保持唯一。附加在祖先类上的角色名称可以被用来在后代里导航。在图13-158里,表达式bb.anE是合法的,因为类B 从类D里继承了角色名称anE。
如果每个关联可以被唯一的确定,那角色名称和关联名称就是可选的。一个 关联名称或者在其末端的角色名称可以确定一个关联。没有必要同时具有这二者,尽管这样也是可以的。如果它是两个类之间的唯一的关联,那关联名称和角色名称都可以省略。大体上说,导航表达式要求一个角色名称。实际上,工具可以从关联的类的名称为生成隐式角色名称提供一个缺省值。
表示法
一个角色名称通过放置在与一个类框相交的关联路径末端的图形字符串来表示。如果角色名称存在,那它就不能被省略。
角色名称可能带有一个可见性的标记--一个箭头--表明在关联远端的元素是否可以看到附加在角色名称上的元素。
291.运行时间(run time)
运行时间就是一个计算机程序执行的时间段。与之相对:建模时间。
292.运行到完成(run to completion)
在其整体中必须完成的转换或者动作序列。
见 动作(action)、原子(atomic)、状态机(state machine)、转换(transition)
语义
在一个状态机里,一定的动作或者动作的序列是原子性的--也就是说,他们不能被其他的动作结束,放弃,或者中断。当一个转换激发,所有附加在它上面的 动作和被它激发的动作必须作为一个组被完成,包括在它进入和离开的状态上的进入动作和退出动作。转换的执行被称为运行到结束,因为它不会等待接收别的事件。
运行到接收的语义可以和普通状态的等待语义进行比较。当一个状态是激活的时候,一个事件可能会引发一个到其他状态的转换。在这个状态里的任何活动都被这个转换所放弃。
一个转换可能由多个段组成,这些段安排成一个链并且由伪状态分隔。几个链可能会合并,也可能会分开,所以整个的模型可能包含由为伪状态分隔 的段的图。
只有链的第一个段可以有一个触发事件。当这个触发事件被状态机处理的时候,转换就会被触发。如果所有段上的监护条件都被满足,转换就能发生,而如果没有别的转换触发,它就会触发。在连续段上的动作被执行。一旦执行开始,在这个链上的所有段上的动作都必须在运行到结束步骤完成之前完成。
在一个运行到结束的转换的执行过程中,引发转换的触发事件作为当前事件对动作是可用的。进入和退出动作可以包含触发事件的参数。各种各样的事件可以引起进入或者退出动作的执行,但是一个动作可以在一个case 语句里区别出当前事件的类型。
鉴于动作的运行到结束语义,他们应该被用来为分配,检验标志,简单的算术,以及别的种类的存书的操作建模。长时间的计算应该建模成可以中断的动作。
293.场景( scenario )
场景就是说明行为的一系列的动作。场景可以被用来说明交互动作或者用例实例的执行。
294.范围( scope )
范围是一个类元成员,如属性,操作或者角色名称。--也就是说,它是在每个实例里代表一个值还是代表这个类元的所有实例的一个共享值。当不带有限定而单独使用时,表示 拥有者范围。( owner scope)
见 生成(creation)、拥有者范围(oxner scrope)、目标范围(target scope)
语义
任何范围都是拥有者范围或者目标范围。
拥有者范围。表示如果在整个类里有一个给定名称的插槽,是否每个类的实例都有一个不同的属性。
实例 每个类元实例有它自己属性插槽的不同副本或者它自己的关联对象的集合。在一个插槽里的值独立与别的插槽里的值。这是缺省的情况。
对于一个操作来说,这个操作作用于一个单独的对象(一个普通的操作)。
类 类元本身有属性插槽的一个副本或者关联对象的一个集合。类元的所有实例共享对一个插槽的访问。
对一个操作,这个操作作用于整个类,比如一个生成操作或者返回关于整个实例集合的数据的操作。这样的操作不能作用于单个的实例。
目标范围。表示一个属性的值或者关联里的目标值是否是实例(缺省)或者类元。
实例 每个属性插槽或者每个关联的连接都包含对目标类元的实例的引用。连接的数目由多重性限制。这是缺省的情况。
类 每个属性插槽或每个关联连接都包含对目标类的一个引用。关联里的信息在建模时间确定并不在运行时间改变,并且它不需要在每个对象里存储。实际上,连接包含类自身,而不是实例。这可能对一些实现信息有用,但是对于大多数建模目的,这种能力可以被忽略。
讨论
类范围属性或者关联为整个类提供全局的值,应该小心使用或干脆不使用,尽管大多数面向对象的语言都提供了。问题在于他们实现全局信息,违反了面向对象的设计原则。另外,全局信息在分布式系统里易出问题,因为这就强制集中式访问而类对象可能分布在许多机器上。最好是引入显式的对象以保存需要的共享信息,而不是使用把类作为带有状态的对象。模型和费用都是明显的。
构造函数(生成操作,工厂操作)一定具有类级的源范围因为这是还没有他们可以操作的实例。这是必要而正确的类范围的使用方法。别的类级 的源范围操作具有和属性一样的困难--也就是,它们暗含关于一个类的实例的集中全局信息,这在分布式系统中是不实际的。
目标范围的可用性具有限制,只能被用在特殊的情况下--通常,只用在细节性的编程目的。
295.自转换( self-transition )
源状态和目标状态相同的转换。它被认为是一个状态改变。当它激发,源状态退出然后重新进入,所以进入动作和退出动作被激发。自转换不同于内部转换,因为在内部转换里没有状态的改变发生。
296.语义变更点(semantic variation point)
语义变更点就是在元模型语义发生变更的点。它为元模型语义的解释提供了一定的自由。
讨论
语义的相同执行并不是对所有应用都适合。不同的编程语言以及不同的目的要求语义的变更,有的是微妙的,有的较大。语义变更点是个问题,因为各个建模人员和各个执行环境对特定语义的理解不能达成一致。通过只是识别和命名语义变更点,就可以避免一个系统的"正确"语义的争论。
例如,是否允许多重类元或动态类元的选择就是一个语义变更点。每种选择都是一个语义变更。其他语义变更点的例子包括一个调用是否可以返回多个值以及类在运行事件是否作为实际对象存在。
297.语义( semantics)
语义就是某事物的涵义及行为的正式声明。
298.发送( send )
即发送者对象生成一个信号实例并把它传送到接收者对象以传送信息。
发送使用依赖性把发送信号的操作或方法或者包含这样的操作或方法的类同接收这个信号的类联系起来。
见 信号(signal)。
语义
发送是对象能够执行的特殊操作。它声明一个待发送的信号,这个信号的一系列参数,以及接收这个信号的目标对象的集合。
一个对象发送一个信号到一个对象集合--通常是包含单一对象的集合。"广播"可以认为是发送一个信息到所有对象的集合,尽管为了提高效率广播可能以特殊的方式实现。如果目标对象的集合包含多个对象,信号的副本会同时发送给集合中的每个对象。如果集合是空的,没有信号被发送。这不是个错误。
生成一个新对象可以被认为是发送一个消息到工厂对象,例如类,这个对象生成新的实例并把消息发送给它作为它的"出生事件"。这为产生者同它的生成物之间的通讯提供了一种机制--出生事件可以认为是从产生者到新对象,同时带有实例化新对象的副作用。
图13-159显示了使用文本语义和图形语义的对象生成。如果目标语言,如C++,不支持类作为运行时间对象,那这种方法就可以使用。在这种情况下,生成动作被编译(在其普通性上强加了一些限制--例如,类名必须是一个字面值)但是低层是相同的。
发送依赖是从这个信号的发送者到信号的接收者的使用依赖的构造型。
文本表示法
在一个转换中,尽管信号发送只是一种特殊形式的动作,但它由它自己的语法。在动作序列中,发送表达式语法为
send 目的地表达式.目的消息名(参数列表)
关键字send 是可选的。调用和发送可以通过消息名称的声明加以区分。但有时显式的区分是很有帮助的。
目的地表达式的计算值必须是对象集合。只有一个对象的集合是合法的。消息发送到集合中的每个对象。
目的消息名是被目标对象接收的信号或者操作的名称。那些参数是其计算值与事件或操作的声明参数相兼容的表达式。信号和操作的区别在于信号的声明在包中而操作的声明在目标类中。在内部模型里没有二义性。
举例
这个内部转换用光标位置在窗口中选择一个对象,然后发送一个 highlight 信号给它。
Right-mouse-down ( location ) [ location in window]
/object := pick-object ( location ) ; send object.highlight ()
图形表示法
消息的发放也可以用图形符号来表示。
状态机之间的消息发送可以通过从发送者到接收者划一个虚线箭头来表示。箭头上标有信号名称和消息的参数表达式。状态图必须包含在代表系统里的对象或类的矩形框里。从图形上来说,状态图可以在对象符号内嵌套,或者是隐式的并在别的地方显示。状态图代表了合作对象的控制。交互作用可能是合作的角色,或者是表明类对象之间相互通讯的普通方式的类。
图13-160包含显示在三个对象之间发送信号的状态图。应该注意这种表示法也可以用在别的图中以显示类或者对象之间的发送事件。
发送符号(在箭头的尾部)可能是一个
类 消息由这个类的对象在它声明周期的某点发送,但是具体细节没有说明
转换 消息作为激发转换的动作的一部分发送(图13-159 和 13-160)。这是消息发送的文本语法的另一种表示法。
接收符号(在箭头的头部)可能是一个
类 消息被对象接收到并且可能在这个对象内触发一个转换。类符号可能包含一个状态图(图13-160)。接收对象可能具有使用相同的事件作为触发的多个转换。当目标对象是动态计算出的时候,这种表示法是不可能的。在这种情况下,必须使用文本表达式。
元类 这种表示法被用来为类范围的操作-如新实例的生成-的激发建模。这样一个消息的接收会引起在它的缺省初始状态下实例化一个新对象。接收者所看到的事件可以被用来从它的缺省初始状态触发一个转换,所以也就代表了从产生者向新对象传递信息的一种方法。
转换 这个转换必须是类里唯一使用这个事件的转换,或者至少是唯一能被这个特定消息发送所激发的转换(图 13 - 159)。当被触发的转换依赖于接收对象的状态时,这种表示法是不行的。在这种情况下,箭头必须画到类上。
发送依赖。发送依赖通过一个从发送信号的操作或类到接收信号的类的虚箭头表示。构造型<sends>附加在箭头上。
299.发送者(sender)
传送一个消息实例到接收对象的对象。
见 调用(call)、发送(send)
300.顺序图(sequence diagram)
显示对象之间以事件顺序安排的相互作用的图。它着重显示了参与相互作用的对象和所交换消息的顺序。
见 激活(activation)、合作(collaboration)、生命线(lifeline)、消息(message)
语义
顺序图代表了一个相互作用-在以时间次序安排的对象之间的通讯的集合。不同于合作图,顺序图包括时间顺序但是不包括对象联系。它可以以描述形式存在(描述所有可能的场景),也可以以实例形式存在(描述一个实际的场景)。顺序图和合作图表达了相似的信息,但是它们以不同的方式显示。
表示法
顺序图具有两个方向:垂直方向代表时间;水平方向代表参与相互作用的对象(图13-161 和 图13-162)。通常,时间沿叶面向下延伸(如果需要,坐标轴也可以反转)。通常只有消息的顺序是重要的,但是在实时应用中,时间轴可以是一个实际的测量。对象的水平次序没有重要意义。
每个对象显示在单独的列里。一个对象符号(带有对象名称的矩形框)放置在代表生成这个对象的消息的箭头的末端,其垂直位置表示这个对象第一次生成的时间。如果一个对象在图的第一个操作之前就存在,对象符号就画在任何消息之前处在图的顶部。从对象符号画一条虚线到对象销毁的那一点(如果销毁发生在概图表示的范围内)。这条线称为生命线。一个大的X放在对象停止存在的那一点,即,或者放在表示销毁对象的消息的箭头的头部,或者放在对象自己销毁的那一点。对于对象活动的任何阶段,生命线加粗到两倍的实心线。这包括主动对象的整个生命和被动对象的激活--对象的某个操作执行的阶段,包括这个操作等待它所调用的操作返回的时间。如果这个操作直接活间接的递归调用它自己,另一条两倍实心线覆盖在它上面以表示双重激活(可以是多于两个)。对象的相对次序没有重要意义,尽管合理的安排它们以使消息箭头所覆盖的距离最小是有帮助的。对激活的注释可以放在附近的空白处。
每个消息显示为一个从发送消息的对象的生命线到接收消息的对象的生命线的水平箭头。在箭头相对的空白处放置一个标号以表示消息被发送的时间。在许多模型中,消息被认为是瞬时的,至少是原子 的。如果一条消息需要一定的时间才能到达,消息箭头就应该对角的向下画以使接收时间晚于发送时间。两端都可以有标号来表示消息接收或者发送的时间。
对于主动对象之间的异步控制流,以两倍实心线表示对象,而用箭头表示消息。两个消息可以同时发送,但是两个消息不能同时接收,因为无法保证同步接收。
图13-161显示了异步顺序图。图13-162 显示了 顺序图上的控制过程流。当为控制过程流建模时,对象产生对一个调用的控制直到返回。调用以实心箭头表示。调用箭头的头部可能激发一个活动或者是一个新的对象。返回以虚线显示。返回箭头的尾部可能结束这个活动或者对象。图13-163 顺序图中的对象状态图13-163显示了一张电影票的生命周期中的状态。生命线可能被状态符号中断以表示状态的改变。这对应于合作图中的变成转换。可以画一个箭头到状态符号上以表示引起状态变化的消息。
注意这种表示法的大部分都是直接从Bushmann,Meunier,Rohnert .Sommerlad和Stal[Buschmann-96]的对象消息顺序图表示法得来的,而对象消息顺序图表示法本身又是从消息顺序图表示法继承修改而来的。
顺序图也可以以描述形式显示,在这种方法里组成元素是角色而不是对象。这样的图显示普通的情况,不是单个的执行。描述级的图没有下划线,因为这里的符号代表角色不是单个的对象。
301.顺序数(sequence number)
在一个表示相互作用中消息的相对执行次序的合作图中,消息标号的文本部分就是顺序数。顺序数可以表示嵌套调用中的消息的位置,控制线程的名称,以及条件递归执行的声明。
见 合作(collaboration)、消息(message)、发送(send)
302.信号(signal)
对象之间异步通讯的声明。信号可以带有表示为属性的参数。
见 事件(event)、消息(message)、发送(send)
语义
信号是以对象之间显式通讯为目的的显式的命名类元。它具有一个显式的参数列表,表示为它的属性。它被显式的从一个对象发送到另外一个对象或者对象 的集合。普通的信号广播可以被认为是发送一个信号到所有的对象--尽管实际上为了效率上的原因,广播通常以别的方式实现。发送者在发送的时候声明信号的参数。发送一个信号等同于初始化一个信号对象然后把它转换到目标对象的集合。信号的接收是为了在接收者的状态机中激发转换。发送到对象集合的信号可能在每个对象中独立的激发零个或者一个转换。信号是对象之间异步通讯的显式方式。要进行同步通讯,必须使用两个信号,每个方向用一个。
信号是可泛化的元素,子信号从父信号继承来。它继承父亲的属性,也可以增加它自己的属性。子信号可以激发声明为使用它的祖先信号的转换。
信号声明在它被声明的包里具有范围。它没有被限制在单个类里。
类和接口都可以声明它准备处理的信号。这种声明是一个接收,它可能包含对信号被接收是期望的结果的声明。这个声明具有说明它是否是多态的一个属性。如果它是多态的,它的后代可以处理这个信号,这就可能会使这个信号无法到达现在的类。如果它不是多态的,它的后代都不能截获这个信号的处理。
信号是一种类元,可以又访问和修改属性的操作。所有的信号共享隐式的操作:发送。
Send(目标集合)
信号被发送到目标集合里的每个对象。
表示法
构造型关键字<signal> 放在具有某信号名称的操作声明前面以表示类或者接口接收这个信号。信号的参数包含在在声明中。声明不一定要有返回类型。信号的声明可以表示为一个类符号的构造型。关键字<signal>出现在信号名称上面的矩形框里。信号的参数作为属性出现在属性部分。操作部分可能包含访问操作。
图13-164显示了使用泛化表示法把一个子信号联系到它的父信号上。子信号继承它的祖先的参数同时也可以增加自己的参数。例如,信号MouseButtonDown 具有属性Time,device,location 。要使用信号作为一个转换的触发,使用下面的语法:
事件名称(参数列表)
而参数列表具有下面的语法:
参数名称:类型表达式
信号参数被声明为可以带有初始值的属性,这个初始值可以在初始化或者发送时被覆盖。如果一个信号实例被生成,初始化,然后发送到一个对象,这时就使用初始值。如果信号使用操作调用语法发送,那初始值就是信号参数的缺省值。
讨论
信号是对象之间最基础的通讯,比过程调用具有更为简单和清楚的语义。信号内在的就是从一个对象到另一对象的单方向异步通讯,所有的信息通过值来传递。它适合于为分布式并发系统建模。
为建立同步通讯,使用一对信号,每个方向使用一个信号。一个调用可以认为是带有隐式返回指针参数的信号。
303.信号事件(signal event)
即一个对象对发送给它的信号的接收事件,它可能会在接收对象的状态机内触发转换。
304.特征(signature)
特征就是行为特征的名称和参数属性,比如操作或者信号。特征带有可选的返回类型(操作有,而信号没有)。
语义
操作的特征是它的声明的一部分。一些(不是全部)特征被用来匹配操作和方法以检查抵触或者重载。为达到这个目的,我们使用操作的名称和参数类型的顺序列表,但是不包括参数名称和方向,返回参数也被排除在外。如果两个特征匹配但是其他的属性不一致(例如,一个in 参数对应一个out 参数),那声明出现抵触,模型就是错误的。
305.简单状态(simple state)
其中没有嵌套状态的状态。嵌套状态的集合形成一颗树而简单状态就是树叶。简单状态没有子结构。它可能具有内部转换,进入动作和退出动作。对比:复合状态。
306.简单转换(simple transition)
具有一个源状态和一个目标状态的转换。它代表了对独占状态区域内的状态转换事件的响应。它的执行结构不会改变并发的数目。
307.简单类元( simple classification)
简单类元就是每个对象都恰好有一个直接类的执行体制。它是大多数面向对象编程语言的执行模型。是否允许单一类元还是多重类元是一个语义变更点。
308.单一继承(simple inheritance)
每个对象只允许有一个父亲的泛化的语义变更。是否允许单一继承或者多重继承是一个语义变更点。
309.单实例类(singleton)
单实例类就是只有一个实例的类(通过声明)。单实例类是在面向对象的框架内在应用中代表全局信息的一种方式。
语义
每个应用必须有至少一个单实例类(通常是隐式的)以为应用建立语境。通常,这个单实例类等同于应用本身,通过计算机里的控制栈和地址空间来实现。
表示法
单实例类以右上角标有一个小"1"的类符号来表示(图13-165)。
这个值代表了系统 里类的多重性。310.快照(snapshot)
快照就是在执行过程中的某时刻,形成系统的配置的对象,连接和值的集合。
311.源范围 (source scope)
插槽被实例拥有还是被类拥有的指示器。
见 范围(scope)
312.源状态(source state)
状态机里转换出发的那个状态。转换应用到源状态上。如果一个对象在这个状态或者在嵌套在源状态里面的状态中,转换就有可能被激发。
313.特化(specialization)
特化就是通过增加子对象产生对一个模型元素的更为特殊的描述。相反的关系就是泛化,泛化也被用来作为特殊元素和一般元素之间关系的名称,因为没有更为合适的术语。子元素是对父元素的特化。相反,父元素是对子元素的泛化。
见 泛化(genaralization)
314.声明( specification )
某事物是什末或者做什末的断言式的描述。例如,用例或者接口就是一个声明。对比:实现
315.建模阶段(stages of modeling )
在设计和建立一个系统的过程中,一个元素或者模型所经历的开发状态。
见 开发过程(development process)
讨论
综合的开发效果可以划分为重点不同的活动。这些活动不是顺序执行,相反,它们是在开发过程的阶段中递归的执行。分析阶段涉及捕获需求和理解系统的需要。设计阶段是在数据结构,算法以及存在的系统的限制下,产生对这个问题的一个实际可行的解决方法。实现阶段涉及在可执行的语言或者介质上构造解决方案(比如数据库或者数字硬件)。配置阶段涉及把解决方案在一特定的物理环境中付诸实施。这种划分方法可能有点武断,并不总是很清楚,但是它保持了有用的指导意义。
但是,这种开发的观点不能等同于开发过程的顺序阶段。在传统的瀑布过程中,它们确实是不同的阶段。在更为现代化的递归开发过程中,它们不能完全区分。在某时间点上,开发活动可能存在于不同的级别,所以它们最好被理解为需要在系统的每个元素上执行的不同的任务,但不是同时执行。
想象一下一组建筑物,它们具有各自的地基,墙壁和房顶;必须为所有建筑物完成所有这些东西,但不是所有的都同时完成。通常,每个建筑的各个部分是按顺序完成的,。但是,有时房顶可以在所有的墙壁完成之前开始。墙壁和房顶的区别偶尔也会消失--考虑一下建在地面上的圆顶。
一些UML元素是为开发的所有阶段准备的。别的元素是以设计或者实现为目的,它们只会在模型足够完善时才出现。例如,属性和操作的可见性声明倾向于在设计阶段出现。在分析阶段主要包括公共成员。
讨论
瀑布开发过程被分为各个阶段,每个阶段在整个系统上执行一次。传统的阶段包括分析,设计,实现和配置。但在一个递归过程中,整个系统的开发不是以互锁的步骤进行的。元素可能以不同的进度开发,每个元素在开发过程中经过相同的阶段,但是不同的元素处以不同的速度进行,所以系统作为一个整体不是处在任何一个阶段。
每个建模阶段处理必须理解和建立模型的领域。早期的阶段捕获更为逻辑和抽象的属性。后期的阶段更注重于实现和执行。分析捕捉系统的需求和专业词汇。涉及捕捉理想条件下的抽象实现的算法和数据结构。它也可能涉及到效率,计算复杂性,以及为建立一个支撑系统的软件工程方面的考虑。实现在真实的编程语言和真实的条件下为系统产生一个操作描述。包括真实语言的缺陷和把系统分解为可以独立开发和存储的部分。运行时间处理资源的并发,计算环境,以及大规模运行。
UML含有一系列适合于各个不同开发阶段的结构。一些结构(例如关联和状态)对所有的阶段都是有意义的,一些结构(例如导航性和可见性)在设计阶段是有意义的但在分析阶段只展示一些不必要的实现细节。这并不排除在工作的较早阶段对它们进行定义。一些结构(例如特定的编程语言语法)只在实现阶段有意义,如果过早的引入会防碍开发过程。模型在开发过程中会改变。一个UML模型在开发的不同阶段呈现不同的形式。,着重于不同的UML结构。建模时首先应该理解不是所有的结构都在所有阶段有意义。
见 开发过程(development process),可以找到关于建模阶段和开发阶段之间联系的讨论。
316.状态( state )
对象的生命中的满足一定条件,执行一定操作,或者等待某事件的条件或者情况。
见 活动(activity)、活动图(activity graph)、复合状态(composite state)、进入动作(entry action )、退出动作(exit action)、结束状态(final state)、内部转换(internal transition)、伪状态(pseudostate)、状态机(state machine)、子状态机(submachine)、同步状态(synch state)、转换(transition)
语义
一个对象在它的生命周期里有一系列的状态。对象保持在某个状态一段有限的时间。为了方便可以引入虚设的状态,它们执行琐屑的动作然后退出。但是这不是状态的主要目的,而且虚设的状态可以被去掉,尽管它们对于避免重复是有用的。
状态包含在状态机里,状态机描述对象响应事件而发展的历史情况。每个状态机描述某个类的对象的行为。每个类可以有一个状态机。转换描述了处于某状态的对象对事件的接收作出的响应:对象执行一个付在转换上的可选的动作而变到一个新的状态。每个状态有自己的转换集合。
动作是原子的,不能被中断。动作依附于转换--状态的改变,而转换本身也原子的不能中断。即将触发的活动可能和一个状态关联。这样的活动被声明为嵌套的状态机或者是一个 Do 表达式.。即将触发的活动可以以一对动作表示,一个在进入这个状态时激发活动的动作和一个在从这个状态退出时结束这个活动的动作。
状态可以分组而成为复合状态。复合状态上的任何转换会影响里面的所有状态,所以以相同的形式影响很多子状态的事件可以通过一个转换处理。复合状态可以是顺序的或者并行的。在顺序复合状态中只有一个状态可以是活动的。在并发复合状态中,所有的状态同时处于活动。
为促进封装,复合状态可以具有初始状态和结束到它的状态。它们是伪状态,目的是为了优化状态机的结构。到复合状态的转换代表初始状态的转换,这等同于直接到它的初始状态的转换,但是这个初始状态可以在外部使用而不用知道它的内部结构。到复合状态里的结束状态的转换代表了在这个闭合状态里活动的完成。在闭合状态里的活动的完成激发这个闭合状态上的活动事件的完成,并引发在这个闭合状态上的完成转换。完成转换是没有显式触发事件的转换(或者,更精确的说,尽管没有显式建模,它以这个完成事件作为隐式触发。)。对象的最外面的状态的完成对应它的死亡。
如果状态是并发的复合状态,所有它的并发子区域必须在复合状态上的完成事件发生前完成。换句话说,从复合并发状态触发的完成转换代表它的所有并发子线程的控制结合。它等待所有的子线程完成才进行。
结构
状态具有下列部:
名称。即状态的名称,它必须在闭合状态中保持唯一。名称可以省略,这样就生成一个匿名状态。任何数目的不同匿名状态可以共存。一个嵌套的状态可以通过它的路径名称确定(如果所有的闭合状态都有名称)。
子状态。如果一个状态机具有嵌套的子结构,它就被称为复合状态。复合状态或者是分离的子状态构成的网络(也就是说,依次活动的子状态)或者是并发子状态的集合(也就是说,并发活动的子状态)。没有子结构的状态(出去可能的内部动作)是一个简单状态。
进入和退出动作。状态可能具有进入和退出动作。这些动作的目的是封装这个状态,这样就可以不必知道状态的内部状态而在外部使用它。当进入状态时,进入动作被执行,它在任何附加在进入转换上的动作之后而在任何状态的内部活动之前执行。状态退出时执行退出动作,它在任何内部活动完成之后而在任何付在离开转换上的动作之前执行。在经过几个状态范围的转换上,几个进入和退出动作可能以嵌套的方式执行。首先,退出动作被执行,从最内层的状态开始向最外层的状态进行,然后,转换上的动作被执行,之后进入动作被执行,从最外层的状态开始以最内层的状态结束。
图13-166显示了激发一个经过几个状态范围的转换的结果。进入和退出动作不能通过任何方式避免即使有异常发生。它们为状态机的行为生命提供了一种封装机制,并且能够保证必要的动作在任何情况下都能执行。内部活动。状态可以包含描述为表达式的内部活动。当状态进入时,在进入动作完成之后活动就开始。如果活动结束,状态就完成。然后一个从这个状态出发的转换被触发。否则,状态等待触发转换以引起状态的改变。如果在活动正在执行时转换触发,活动被结束并且退出动作被执行。
内部转换。状态可能包含一系列的内部转换,它们除了没有目标状态不能引起状态转换外其他的和普通转换相象。如果对象的事件在对象正处在拥有转换的状态时发生,那内部转换上的动作被执行,但是没有状态的改变也没有进入和退出动作发生,即使内部转换在闭合的状态里声明(因为状态没有改变)。这一点于自转换不同,在后者中,一个从一个状态到同一个状态的外部转换发生,结果会执行所有嵌在具有自转换的状态里的状态的退出动作,执行它自身的退出动作,执行它的进入动作。在转向当前状态的自转换上,动作被执行,退出然后重新进入。如果当前状态的闭合状态的自转换激发,那结束状态就是闭合状态自己,而不是当前状态。换句话说,自转换可以强制从嵌套状态退出,但是内部转换不能。
子状态机。状态体可以代表通过名称引用的分离的状态机的副本。被引用的状态机被称为子状态机因为它被嵌套在更大的状态机里,产生引用的状态被称为子状态机状态。子状态机可以附加在为动作提供语境的类上,例如可以读写的属性。子状态机的目的是在很多状态机里重用以避免重复相同的状态机的片段。子状态机是一种状态机的子路线。
在子状态机的引用状态里,子状态机通过带有可能的参数列表的名称引用。这个名称必须是一个具有初始和结束状态的状态机的名称。如果子状态机在它的初始转换上有参数,那参数列表必须具有匹配的参数。当进入子状态机时,首先执行进入动作,然后 从初始状态开始执行子状态机。当子状态机到达它的结束状态时,这个子状态机的任何退出动作被执行。这个子状态机就认为是执行完毕,可能会引起以隐式的完成活动为基础的转换。
到子状态机的引用状态的转换激活目标子状态机的初始状态。但是有时设计转向子状态机里其他的状态的转换。桩状态是一个放在子状态机引用状态里的伪状态。转换可以从主状态机里的其他状态连向桩状态。如果转向状态的转换激发,子状态机副本里的引用状态变成活动的。
子状态机代表状态里嵌套可中断的活动。这等同于以唯一的子状态机的副本代替子状态机状态。不用提供状态机,而可以在子状态机上附加过程表达式(这时一种活动)。活动可以认为是一系列状态的定义,每个原始表达式定义一个状态,在任何两步之间都可以中断。它不同于动作,动作是原子和不可中断的。
动态并发。活动状态和子状态机状态可能具有多重性和并发性表达式。多重性 说明了状态的多少个副本可以并发执行。普通情况是仅为一的多重性,意味着这个状态代表了普通的控制线程。如果多重性的值没有固定,这就意味着执行的数目在运行时间动态的确定。例如,值 1..5 意味着从一到五份活动的副本并发执行。如果存在并发性表达式(如果并发性不是一,那这就是必须的),那在运行时间它必须在参数列表的集合上计算。集合的基数表示状态的并发活动的数目。每个状态接收不同的参数值列表作为它的当前事件的值。活动的动作可以访问当前事件的值。当所有的执行都已经完成,动态并发状态被认为是完成,执行转到下一个状态。这种能力是为了活动图。
可推迟事件。状态里推迟发生的状态的列表,如果它们不触发转换,直到它们触发转换或者系统产生一个到它们不被推迟的状态的转换 ,它们才被重新执行。这种推迟事件的实现包含内部事件的队列。
表示法
状态显示为圆角矩形。它可能有一个或者多个部分。这些部分是可选的。可以包含下面的部分。
名称部分。容纳状态的(可选)名称作为一个字符串。没有名称的状态是匿名的,而且互不相同。但是不能在同一个图里重复相同的命名状态符号,因为这是使人混淆的。
嵌套状态。显示复合状态本身的由附属嵌套的状态构成的状态图。状态图在外部状态的范围里画出。转换可能直接连接到嵌套状态,还可以连到外部状态的范围。
在互斥的区域里,子状态直接在复合状态里画出。在并发的区域里,并发状态符号以虚线分为子区域(也就是,它是平铺的)。
细节和例子可参看复合状态。
内部转换部分。容纳一系列活动或者动作。这些活动或者动作是在对象处于该状态时,接收到事件而作出响应执行的,结果没有状态改变。内部转换具有下面的形式
事件名称(参数表)[监护条件]/动作表达式
动作表达式可以使用拥有对象的属性和连接以及进入转换的参数(如果它们出现在所有的进入转换里)。
参数列表(包括圆括弧)如果没参数就可以省略。监护条件(包括方括弧)和动作表达式(包括斜杠)是可选的。
进入和退出动作使用相同的形式但是它们使用不能用作事件名称的相反的词汇:进入和退出。
进入/动作表达式
退出/动作表达式
进入和退出动作不能有参数或者监护条件(因为它们是隐式激活的,而不是显式调用)。为了在进入动作上获得参数,当前事件可以通过动作访问。这一点在获得新对象的生成参数时特别有用。
保留动作名称defer 表示在状态或者子状态里可以推迟的事件。内部转换不能有监护条件或者动作。
事件名称/defer
保留字do 代表非原子活动的表达式。
Do/活动表达式
子状态机引用状态。嵌套子状态机的激发通过状态符号体里的具有下面形式的 符号串显示。
Include / 状态机名称(参数列表)
Do-活动和子状态机都是描述非原子的计算,这些计算通常运行到它们结束但是可以被激发转换的事件中断。
举例
图13-167 显示了带有内部转换的状态。图13-168显示了子状态机的声明和使用。动态并发。具有不为1的值的动态并发以状态符号右上角的多重性符号串表示。对于普通顺序的执行,这个符号串不应该被包含。这种表示法主要为了活动图而在状态图里应该尽量避免。
317.状态机(state machine )
对象或者交互在其声明周期里为响应事件而经历的状态的顺序的声明,连同它的响应事件。状态机附加在一个源类,合作或者方法上,声明源元素实例的行为。
见活动图(activity graph)、复合状态(composite state)、事件(event)、伪状态(pseudostate)、状态(state)、转换(transition)
语义
状态机是一个状态和转换的图,描述了某类元的实例对事件接收的响应。状态机可以附加在一个类元上,例如类或者用例,还可以附加在合作和方法上。状态机所附的元素被称为状态机的母机。
一个完整的状态机是一个已经被分解为子状态的复合状态。最里面的简单状态没有子状态。
状态机执行语义(State machine execution semantics)
状态机执行语义在本文的其他部分进行讨论。注意下面的部分描述状态机执行的语义效果不应该被认为是一种实现方法。有很多方式实现这些语义,。多数语义在其他的文章里描述,但是为了方便我们把它们收集在这里。
在对象或者其他实例的状态机的活动状态配置里,在任何时候都存在一个或者多个活动的状态。如果一个状态是活动的,那离开这个状态的转换可能会激发,引起一个动作的执行,使得一个状态代替原来的状态。多个活动的叶状态表示内部并发性。状态机的结构和状态的的转换对可以并发活动的状态施加了限制。简单的说,如果一个顺序复合状态处于活动,只有一个互斥的直接子状态必须处于活动。如果一个并发复合状态处于活动,每个直接子状态都必须处于活动。
转换触发和动作(Transition firing and action)
首先作一个基本 的假设:状态机在某一时刻处理某个事件而在处理另外的事件之前必须完成这个事件的结果。也就是说,事件处理过程中,事件之间不能相互作用。这就是 "运行到完成"处理方法。这并不意味着所有的计算都是不能中断的。一个通常的扩展计算可以分为一系列原子步骤,在任何步骤之间外部事件可以中断这个计算。这与计算机里的物理情况非常相似,中断可以发生在分离的小步骤之间。
一个推论假设是事件是异步的。两个事件不能在完全相同的时间发生--更为精确的说,如果两个时间恰在相同的时间发生,这只是巧合,可以用任何相对次序处理它们而不会丢失任何一般性。不同的执行次序所产生的结果是不同的--竞争条件是并发系统的一个重要特性-但是不能在分布式系统里假定同时性。任何作这个假定的计算在逻辑和物理上都存在缺陷。并发执行要求在分布式系统里的独立性。
概念上来讲,动作是瞬间的,没有两个事件是同时的。在一个具体的实现里,动作的执行需要一定的时间,但是重要的是动作是原子而不可中断的(概念上)。如果一个对象在执行动作的期间接收到一个事件,这个事件就被放到一个队列中直到动作执行完成。
只有在没有动作被执行时才会处理事件。如果动作发送一个信号到另外一个对象,那这个信号的接收不是同步的。它在动作和转换完成后象另外一个事件一样被处理。对一个操作的调用会挂起调用者直到操作被执行。它可以被实现为触发接收者的状态机的方法或者调用事件,可由接收者选择。为避免长时间不能处理事件,动作应该简单一点。动作不应该为受保护区域或者长时间可中断计算建模,它们应该设计为子状态机或者是嵌套活动状态。这允许事件处理,也允许嵌套计算被中断。如果在一个真实的系统里包含一个长时间的动作,那事件有可能无法及时处理。这就是糟糕的模型的后果。动作与可能发生的事件所要求的响应时间相比必须简短。
当对象没有执行动作时,它应该立即处理它所接收的事件。概念上来说,动作是瞬间的,但是实际上它需要一定的时间,所以新的时间必须放在对象的一个队列里。如果队列里没有事件,对象会等待直到有事件出现然后处理它。概念上来说,对象每次只处理一个事件。这不会称为限制因为动作被认为时原子的,十分简短。在实际的实现里,事件可能以确定的次序存放在队列里。但是,UML语义并没有声明处理并发事件的次序,建模者也不应该假定。如果事件必须以某一特定的次序处理,那状态机必须合理建造以强制实施这种次序。物理实现应该选择一些简单的排序规则。
在对象处理事件时,它的活动状态配置可能包含一个或者多个活动并发状态。每个状态接收一份独立的事件副本并独立的响应。并发状态的转换独立触发。一个子状态可以改变而不影响其他的状态,有一种例外的情况就是负责转换,例如分叉或者结合(在后面描述)。
对于对象的每个活动状态,状态的离开转换作为触发的候选者。如果类型与候选转换的触发事件相同或者是它的子孙事件被处理,那候选转换就会触发。转换不会被祖先事件所触发。当事件被处理而激发一个转换时,监护条件被计算求值。如果监护条件的值为真,转换就被允许。监护条件的布尔表达式中可能包含触发事件的参数,以及对象的属性。应该注意监护表达式不可以产生副作用。也就是说,它们不能改变对象的状态或者系统的其余部分。所以,它们的计算次序与结果无关。只有处理事件时才计算监护条件。如果它的计算值为假,如果其中的变量在以后改变了值,也不会重新计算。
为构造复杂条件,可以用多个分段给转换建模。第一个分段有触发事件,紧跟着是具有监护条件的分叉树。树的中间节点是伪状态,是为构造转换而存在的假状态,但是它们在运行到完成步骤的末尾不能保持活动。每个经过分段树的可能路径被认为是独立的转换,独立的可执行。每个分段不能单独激发。一系列分段上的监护条件必须为真否则转换(包括它的所有分段)根本不会激发。实际上,分支点上的监护条件经常分割可能的结果。所以,一个实际的实现可以每次处理多段转换的一个步骤,但不是总是如此。
如果没有转换被允许,事件就被忽略。这不是错误。如果恰有一个转换被允许,它就会激发。如果来自单个状态的多个转换被允许,它们中的一个被允许。如果没有声明任何的限制,那选择就是不确定的。不能假定选择是公平的,可以预测的,或者是随机的。实际的实现可能会提供解决冲突的规则,但是建模者应该作出显式的意图,而不应该单纯依赖这些规则。不论是否有转换触发,事件都消灭。
离开一个活动状态的转换有资格被触发。另外,包含活动状态的复合状态是触发的候选者。这可以认为是通过嵌套状态而进行的转换继承,类似于通过子类进行的操作继承。只有内部状态上没有转换激发时,外部状态上的转换才有资格激发。否则,外部转换会被内部转换掩盖。
当转换触发时,附加在它上面的任何动作都会执行。动作表达式可以使用触发事件的参数以及可以到达的母对象(owning object)的属性和值。并发状态应该是独立的,应该在不同的值集合上作用。任何相互作用应该使用信号显式进行。如果两个并发状态必须同时访问一个共享的资源,它们必须显式的向资源发送信号,资源作出仲裁。具体的实现里可能会去掉这样的显式的通讯,但是必须小心防止出现无意义或者危险的冲突。并发动作确实访问了共享值,那结果是不确定的。
如果穿过复合状态的范围的转换被激发,进入或者退出动作可能会被执行。当转换的源状态和目标状态处在不同的复合状态时就会发生范围穿过。当激发的转换是从一个外部复合状态继承来的而强制对象从一个或多个内部状态退出时,也会发生范围穿过。注意内部转换不会引起状态的改变所以从来不会引发进入或者退出动作。
为确定正在执行的进入和退出动作,找出对象的当前活动状态(可能嵌套在具有该转换的复合状态里)和转换的目标状态。然后找到封装了当前状态和目标状态的最内层复合状态。把它叫做公共祖先。当前状态和直到公共祖先的任何闭合状态(但不包括公共祖先)的退出动作被执行,最内层的首先执行。然后转换之上的动作执行。在这之后,目标状态和直到公共祖先的任何闭合状态(但不包含公共祖先)的进入动作被执行,外层的首先执行。换句话说,每次从一个状态退出直到到达公共祖先,再依次进入各个状态直到到达目的状态。公共祖先上的进入和退出动作不被执行,因为它没有被改变。这个过程保证每个状态都被很好的封装。
转换上的动作在任何退出动作执行完毕后而在任何进入动作执行之前执行。
注意自转换(一个到其自身的转换)的激发会引起可能处于活动(该转换可能从一个封装的复合状态继承而来)的源状态的任何嵌套状态的退出。这还会引起源状态的退出动作的执行然后是它的进入动作。也就是说,状态退出然后重新进入。如果这不是期望的结果,那应该在这个状态里使用内部转换。这不会引起状态的改变,即使活动状态嵌套在具有这个转换的状态里。
在运行到完成步骤的执行过程中,所有的动作可以访问一个隐式的当前事件,该事件是引发运行到完成序列里的第一个转换的事件。因为可能存在多个可以导致一个动作的执行的事件,动作可以在当前事件的类型上作出区分(象在Ada 或者多态操作)以执行可选的代码分支。
在所有的动作执行完毕,原来的当前状态不在活动(除非它是目标状态),转换的目标状态处于活动,额外的事件可以被处理。
可以把转换构造成有几个其中间节点是结合状态的分段。每个分段具有自己的动作。这些动作可能和整个转换的进入和退出动作产生 交错。对于进入和退出动作,如果分段是一个完整的转换,转换分段上的每个动作在其应该发生的位置发生。参看图13-167,可以发现一个示例。
内部转换(Internal transitions)
一个内部转换具有源状态而没有目标状态。它的激发确实不会引起状态的 改变,即使激活的转换是从一个封装状态继承而来。因为状态没有改变,就没有进入和退出动作被执行。内部转换的唯一结果就是执行它的动作。激发一个内部转换的条件和激发外部转换的条件是相同的。
注意内部转换的激发可能会掩盖使用相同的事件的外部转换。所以,有时定义一个没有动作的内部转换是有目的的。如上所述,在顺序区域里每个事件只激发一个转换,而内部转换具有比外部转换更高的优先权。
内部转换对于处理没有状态改变的事件方面是有用的。
初始状态和结束状态(Initial and final states)
为封装状态经常可以使状态的内部和外部分离。也可以把一个转换连到一个复合状态上而不必知道状态的内部结构。这可以通过使用初始状态和结束状态来完成。状态可能具有一个初始状态和一个结束状态。初始状态是一个伪状态--一个和普通状态有连接的假状态--对象不可能保持在初始状态。对象可以保持在结束状态,但是结束状态不能有任何显式的触发转换;它的目的是为激发封装状态上的完成转换。初始状态必须有一个出发的完成转换。如果存在多个出发的转换,它们必须都缺少触发,而监护条件必须分离可能的值。也就是说,当初始状态被激发时,有且仅有一个出发的转换被触发。一个对象从不会保持在初始状态,而会立刻转换到一个普通的状态。
如果一个复合状态有初始状态,那转换可以直接连接到复合状态作为目标。任何到复合状态的转换都隐式的是一个到复合状态的初始状态的转换。如果复合状态没有初始状态,转换不能把复合状态作为目标。它们必须直接连接到子状态。一个具有初始状态的状态可能有直接连到内部状态的转换,也可以有直接连到复合状态的转换。
如果复合状态具有结束状态,那它可能是一个或者多个出发完成转换的源,出发完成转换也就是缺乏显式事件触发的转换。完成转换是由状态活动的完成隐式允许的转换。一个到结束状态的转换意味着复合状态执行完成。当一个对象转换到结束状态,离开封装的复合状态的完成转换在其监护条件被满足的情况下允许激发。
复合状态也可以拥有出发的转换---即具有显式的事件触发的转换。如果一个引发这种转换的事件发生,状态里的任何正在进行的活动(在任何的嵌套深度)都会被终结,被终结的嵌套状态的退出动作被执行,转换被处理。这样的转换通常用来为异常和错误条件建模。
复合转换(complex transition)
进入并发复合状态的转换意味着一个进入它的所有并发子状态的转换。这可以以两种方式发生。
一个转换可以由多个目标状态,每个并发子状态一个。注意这样的分叉转换仍然只有一个触发事件,监护条件和动作。这是直接声明了每个目标状态的显式的进入复合状态的转换。这代表了一个进入并发子线程的显式控制分叉。
转换可以省略一个或者多个并发子状态的目标,或者它把复合转换本身作为目标。在这种情况下,每个被省略的子状态必须有初始状态以指明它的缺省激发状态。否则,状态机的建造是不好的。如果复合转换激发,显式的目标并发子状态将处于活动,其他的并发子状态的初始状态也处于活动。简单的说,任何进入并发子状态的转换暗示着进入一切没有显式说明的别的对等并发子状态的初始状态的转换。进入复合状态本身的转换暗示着进入它的每个并发区域的初始状态的转换。如一个并发复合状态处于活动,那它的每个子区域都处于活动。
类似的,从任何并发子状态出发的转换暗示着从所有子状态出发的转换。如果事件的发生引起这样的转换激发,其余子状态的活动被终结,他们执行退出动作,转换自己的动作被执行,目标状态变成活动的,这样减少了活动并发状态的数目。
到一个并发子状态的结束状态的转换不会强制终结别的并发子状态(这不是并发子状态外的转换)。当所有的并发子状态到达他们的结束状态,封装复合状态应该已经完成他的活动,任何离开复合状态的完成转换都允许激发。
复杂转换可以具有多个源状态和多个目标状态。在这种情况下,它的行为是上面所述的分叉和结合的综合。
历史状态(History state)
复合状态可能包含历史状态,历史状态本身是个伪状态。如果一个被继承的转换引起从复合状态的自动退出,状态会记住当强制性退出发生的时候处于活动的状态。到复合状态里的历史伪状态的转换表示被记住的状态要重新建立。到别的状态或者封装状态本身的显式转换不能使历史机制和通常的转换规则适用。但是,复合状态的的初始状态可以连接到历史状态。在这种情况下,到复合状态的转换确实(非直接)激发历史机制。历史状态可以有一个没有监护条件的出发完成转换;转换的目标是缺省的历史状态。如果状态区域从来没有进入或者已经退出,到历史状态的转换会到达缺省的历史状态。
存在两种历史状态:浅历史状态和深历史状态。浅历史状态保存直接包含(深度为1)在相同的复合状态里的状态作为历史状态。深历史状态保存在最后一个引起封装复合状态退出的显式转换之前处于活动的所有状态。它可能包含嵌套在复合状态里的任何深度的状态。一个复合状态最多只有一种历史状态。每个状态可能有它自己的缺省历史状态。
如果可以更为直接的建立模型,就应该避免历史机制,因为它过于复杂,而且不是一种好的实现机制。深历史状态尤其容易出问题,应该尽量避免用它支持更显式(也更为易于实现)的机制。
表示法
一个状态图显示一个状态机或者状态机的一部分。状态用状态符号表示,转换用连接状态符号的箭头表示。状态可能通过物理包含和平铺包含子图。图13-169 显示了这样一个例子。
状态图表示法是David Hard 发明的, 并且结合了莫而机(有关进入的动作)和米里机(有关转换的动作)的各个方面,而且增加了嵌套状态和并发状态的概念。
要得到更详细的细节,参看 状态,复合状态,子状态机,伪状态,进入动作,退出动作,转换,内部转换,活动。要探究活动流的合适表示法的另一个变体,请参看 活动图。也可以参看目的是在活动图里使用而在状态图里也可以使用的可选符号的控制图标。
讨论
状态机可以有两种使用方式。所以,他们的含义可以以任何一种来理解。在一种情况下,状态机可以声明它的主元素--典型的是一个类--的可执行行为。在这种情况下,这个状态机描述了它的主元素从整体中的其他部分接收到事件时的响应。响应用转换来描述,每个转换描述主元素在某状态下接收到一个事件的响应。结果描述为一个动作和一个状态改变。动作可以包括向别的对象发送信号而激发它们的状态机。状态机提供了对系统行为的简化声明。
在第二种情况下,状态机可以被用来作为一个协议声明,显示在类或者借口上操作激发的合法顺序。在这样一个状态机里,转换被调用事件激发而它们的动作激发期望的操作。这意味着在这一点,调用者被允许激发操作。协议状态机不包括声明操作本身行为的动作。它显示了在一个特定的顺序下,哪个动作可以被激发。这样的状态机声明了有效的操作顺序。这是状态机在语言(从计算机科学的语言理论)里作为顺序生成器的一个应用。这样的状态机被用来作为系统设计的限制。它不能直接执行,也没有说明如果发生非法的顺序会出现何种情况--因为不应该发生非法的顺序。系统设计者有责任保证只有合法的顺序发生。第二种应用比第一种更为抽象,因为第一种情况在可执行的方式下声明了所有情况的响应。但第二种情况通常比较方便,尤其在高层次和过程编码中。
318.状态机视图(state machine view)
处理各个元素在其生命周期里的行为声明的系统的外貌。这个外貌包含状态机。在动态视图里它和别的行为视图松散的结合在一组里。
319.状态图视图( Statechart diagram)
显示一个状态机--包括简单状态,转换,嵌套复合状态--的图。它的原始概念是由David Hard 发明的。
见 状态机(state machine)
320.静态类元(static classification)
泛化的一个语义变更,在这个语义变更里,对象不能改变类型,也不能改变角色。选择静态类元或者是动态类元是一个语义变更点。
321.静态视图(static view)
展示系统里的事物的特征及它们之间的静态联系的总体模型的视图。它包含类元和它们相互之间的联系:关联,泛化,依赖和实现。有时被称为类视图。
语义
静态视图显示了系统的静态结构,特别是存在事物的种类(例如类或者类型),它们的内部结构,相互之间的联系。尽管静态视图可能包含具有或者描述暂时性行为的事物的具体发生,但静态视图不显示暂时性的信息。
组成静态视图的最上层包括类元(类,接口,数据类型),联系(关联,泛化,依赖,实现),限制和注释。它也可以包含包和子系统作为有组织的单元。别的组成元素隶属于并包含在最高层元素里。
与静态视图相关并且在图里经常与之合并的是 实现视图,配置视图以及模型管理视图。
静态视图可以与动态视图进行比较,动态视图补充静态视图并且建立在静态视图之上。
图13-169
321.构造型(stereotype )
在一个基于已经存在的模型元素的类型的模型中定义的新型的模型元素。构造型可以扩展已存在元模型类的语义,但是不能扩展它的结构。
见限制(constraint)、标签值(tagged value)
参看14章,标准元素(Standard Elements), 以得到预定义构造型的一个列表。
语义
构造型代表了已存在模型元素的一个变更,具有相同的形式(如属性或者联系)但是有不同的目的。通常,构造型代表一个使用区分。除了有不同的外表外,构造型元素可能在基元素的限制之外,还有附加的限制。人们希望代码生成器或者别的工具能够特别的对待构造型元素,例如生成不同的代码。构造型的目的在于 普通的建模工具,例如模型编辑器或者仓库,在大多数情况下把构造型元素作为带有附加文本信息的普通元素看待,而在某些特定的语义操作里,如形式检查,代码生成,报表输出等,才区分不同的元素。构造型代表了UML的内在可扩充机制之一。
每个构造型从一个基本模型元素类继承而来。每个带有构造型的元素都有基本模型元素类的属性。
一个构造型也可以从别的构造型具体化而来。构造型声明是可泛化元素。子构造型有父构造型的属性。最后,每个构造型都建立在某个模型元素类的基础上。
构造型可以有一系列的标签,其中一些具有缺省值,当没有显示声明的标签值时就使用缺省值。可以声明每个标签的允许取值范围。每个带有构造型的元素必须有与标签列表对应的标签值。如果在一个构造型元素里没有显式说明,具有缺省值的标签会自动使用缺省值。
构造型可能在基本元素所具有限制之外,还会有一系列额外的限制,增加新的条件。每个限制应用到每个带有构造型的模型元素上面。每个模型元素也受到应用到基本元素上面的限制的影响。
构造型是一种虚拟元模型类(也就是说,它在元模型类里不明显),它是在模型里增加的而不是通过修改UML的预定义元模型。基于这个原因,新构造型的名称必须不同于存在的UML元类名称或者别的构造型或者关键字。
任何模型元素最多只能有一个构造型。这条规则在逻辑上不是很重要,但是它简化了构造型的语义和表示法而在功能上没有任何实质的损失,因为构造型本身的多重继承是允许的。构造型可以是别的构造型的孩子。如果一个元素具有多个构造型,这种情况下,可以重新设计,使它只具有一个是其他构造型孩子的构造型。这偶尔会迫使建模者生成一个特别的假构造型以合并其他的构造型但是我们认为在平均情况下的简单性弥补了这种偶尔的不方便。
在UML里预定义了一些构造型;其他的可能是用户定义的。构造性是UML中的三个扩充机制之一。
参看 14词汇表,可以看到一系列的预定义构造型。
见限制(constraint),标签值(tagged value)。
表示法
构造型的使用方法的普通表示法是用符号来代表基本元素而在元素名称(如果存在的话)上面放一个关键字字符串。关键字字符串是构造型的名称,它通常放在别描述的元素的名称的上面或者前面。关键字字符串也可以用做列表里的一个元素。在这种情况下,它应用到列表里后面的元素知道别的字符串代替它,或者一个空的构造型字符串清空它。注意构造型名称不能和作用到相同的元素类型上的预定义关键字相同。(为避免混淆,预定义的关键字名称应该避免用在任何构造型上,即使它是用在不同的元素之上,在原则上可以区分)。
为允许UML表示法的有限的图形扩充,可以为构造型关联一个图标或者图形标记(比如质地或者颜色)。UML没有说明图形声明的形式,但是存在很多位图和格式,可以在图形编辑器里使用(尽管它们的可移植性是个问题)。图标可以用在两个方面。一种情况下,它可以在构造型所基于的基本模型元素的符号里代替或者补充构造型关键字字符串。例如,在一个类矩形里,它被放在名称部分的右上角。在这种形式下,条目的普通内容可以在它的符号里看到。另一种方式,整个模型元素符号压缩为一个图标,图标里有元素的名称,或者把元素的名称放在图标的上面或下面。包含在基本模型元素符号里的其他信息被省略。
图13-170 显示了画构造类的各种方法。
UML 避免使用图形标记,比如颜色,因为它们给某些人(色盲)和某些重要的设备(例如打印机,复印机,传真机)带来了挑战。任何UML符号都不要求使用这样的图形标记。用户为了他们自己的目的(例如在某个工具里高亮显示)可以自由使用图形标记,但是应该知道这在交流上的显示,在必要的时候他们应该使用经典的表示法。
构造型声明。构造型的类元层次可以在一个类图里显示。但是,这是一个元模型图,必须和普通的模型图区分开来(通过用户和工具)。在这样的图里,每个构造型显示为带有关键字 stereotype 的类符号(矩形)。泛化关系可以显示扩展的元模型层次(图13-171)。
鉴于扩展内部元模型层次的危险性,具体的工具可以,但不是必须,把这种能力在类图里显示出来。对构造名称的声明不是普通建模者所必须要求的,但是它可以作为一种支持。323.字符串(string)
文本字符的一个序列。字符串表示的细节依赖于实现,可能会包含支持国际字符和图形的字符集合。
语义
很多语义属性,特别是名称,把字符串作为他们的值。字符串是在某适合于显示模型的信息的字符集合里的字符的序列。字符集合可能包含非罗马字母和字符。UML不声明字符串的编码,但它假定编码足够普通以允许任何合理的使用。大体上,字符串的长度应该是没限制的;任何实际的限制必须足够大到不受应用的约束。字符串也应该包含各种人类语言的字符的可能性。标志符(名称)应该全部由一个有限字符集里的字符构成。注释和其他类似的没有直接语义内容的描述性字符串可以包含别的种类的元元素,例如表,图或者视频剪辑,以及别的种类的嵌入文档。
表示法
图形字符串是具有一定实现灵活性的原始表示元素。在某些语言里,它被认为是线性顺序的字符串,可能包含各种类型的嵌入文档。可以期望支持使用多种语言,但是细节留给编辑工具去实现。图形字符串可以是附在直线上的,或者它们也可以附在别的符号上。
字符串被用来显示具有字符串值的语义属性,也用来编码别的语义属性的值以便显示。从语义字符串到表示字符串的映射是直接的。从别的语义属性到表示字符串的映射由语法控制,语法在各种元素的文章里描述。例如,属性的显示表示法对名称,类型,初始值,可见性以及范围进行编码而形成一个显示字符串。
对编码的非正规扩展是可能的--例如,属性可以用C++的表示法显示。但这些编码中,有的会丢失一些模型信息,所以具体的工具应该以用户可选的方式支持它们而同时要保持对正规UML表示法的支持。
字体和字体大小是独立于字符串的图形标记。它们可以为各种模型属性建立代码,这些模型属性有的是由文档所提出的,有的留给工具或者用户。例如,斜体字显示抽象的类和抽象的操作,下划线显示类范围的特性。
特定的工具可以以各种方式处理长字符串,例如截取一个固定的长度,自动换行,以及插入滚动条。我们假定如果需要,可以找到办法保持全部的字符串。
324.结构特征(structural feature)
模型元素的一个静态特征,例如属性或者操作。
325.结构视图(structural view)
一个总体模型的视图,该模型着重于系统里对象的结构,包括它们的类型、关系、属性和操作。
326.桩状态(stub state)
桩状态是子状态机引用状态里的一个伪状态,而这里的子状态机引用状态确定被引用子状态机里的一个状态。
见桩转换(stubbed transition)、子状态机(submachine)、子状态机引用状态(submachine reference state)。
语义
到子状态机引用状态的转换激活目标子状态机的初始状态。但是有时候,希望一个子状态机里到别的状态的转换。转换可以在桩状态和包含状态机的状态之间连接。如果到桩状态的转换激发,在子状态机里的被确定状态被激活。如果子状态机里的某个状态处于活动,那从确定这个状态的桩状态出发的转换就是激发的候选者。相同子状态机引用状态里的桩状态之间的连接是被隐藏的。
图13-172表示法
从桩状态出发或者到桩状态的转换被画成进入或者从子状态机引用状态出发的桩转换--也就是一个箭头,这个箭头在代表子状态机引用状态的状态符号里的一个短条上开始或者结束。该条标记有名称,该名称必须和被引用子状态机里的状态名称匹配。
图13-173显示了子程序引用状态里的一个桩。
图13-174显示了对应的子状态机的定义。
327.桩转换(stubbed transition)
表明转换进入复合状态的一种表示法,但是细节被省略。
见桩状态(stub state)。
表示法
桩显示为在封闭状态范围里所画的一条小的垂直线(图13-172)。桩可以标有状态的名称,但是当细节被省略时,这个名称通常被省略。它表示转换连接到一个省略的内部状态。到初始状态的转换或者离开结束状态的转换不能使用桩。桩表明额外的子状态存在于模型里但是在这个图里被省略。
子状态机引用状态里的桩转换(图13-173)引用对应的子状态机定义里的一个状态(图13-174)。这不是省略细节的一种情形,必须包括桩名称。
328.子类(subclass)
泛化关系里别的类的孩子--也就是说,更为特殊的描述。孩子类被称为子类。父亲类被称为超类。
见泛化(generalization)、继承(inheritance)。
语义
子类继承它的超类的结构、关系和行为,而且可以对它进行增加。
329.子状态机(submachine)
可以作为别的状态机的一部分被激发的状态机。它不是附在类上而是一种状态机子例程。它的语义效果象是复制它的内容而后插入引用它的状态。
见状态(state)、状态机(state machine)、子状态机引用状态(submachine reference state)。
子状态机引用状态(submachine reference state)
引用子状态机的一个状态,子状态机的一个副本,它为代替子状态机引用状态而成为闭合状态机的一部分。它可以包含桩状态,桩状态确定子状态机里的状态。
见状态(state)、状态机(statemachine)、桩状态(stubstate)。
语义
子状态机引用状态等同于插入子状态机的一个副本以代替引用状态。
表示法
子状态机引用状态被画为带有下面形式的标记的状态符号:
include 子状态机名称
可以向子状态机引用状态里的桩状态画转换箭头。它们被画为桩转换--也就是说,在一个交叉条上结束的箭头。交叉条标有被引用子状态机里的一个状态的名称。
举例
图13-173显示了带有子状态机引用状态的状态机的一部分。该状态机向有帐户的顾客售票。它必须确认顾客的身份,这是它的工作的一部分。确认顾客是别的状态机的要求所以它被作成一个独立的状态机。图13-174 显示了状态机 Identify的定义,它被用做别的状态机的子状态机。进入子状态机普通方式会读顾客的卡,但还存在一种显式的进入状态,有办公职员手动的输入顾客的名字。如果确认过程成功,子状态机在它的结束状态终结。否则,它转到状态 Failure。
在图13-173里,子状态机引用显示为带有关键字Include 和子状态机名称的状态图标。子状态机的普通进入方式显示为指到它的边界的箭头。这个转换激活子状态机的初始状态。普通方式的退出显示为从它的边界出发的完成转换。如果子状态机正常结束就会激发这个转换。
显式状态ManualEntry 的进入通过到子状态机引用符号里的一个桩的转换来显示。该桩标有子状态机里的目标状态的名称。相似的,从显式状态Failure的退出通过从一个桩出发的完成转换来显示。到桩的转换可以被触发或者是无触发的。
331.子状态(substate)
作为复合状态一部分的一个状态。
见复合状态(composite state)、并发子状态(concurrent substate)、互斥子状态(disjoint substate)。
332.可替代性规则(substitutability principle)
给定其类型声明为X的变量或者参数的一个定义,任何X后代的实例都可以用做实际值而不会违反声明和使用的语义,这就是可替代性规则。换言之,后代元素的实例可以替代祖先元素的实例。(这是 Barbara Liskov 的贡献)
见泛化(generalization)、实现继承(implementation inheritance)、继承(inheritance)、接口继承(interface inheritance)、多态(polymorphic)、私有继承(private inheritance)。
讨论
可替代性规则的目的是使多态操作可以正常工作。这不是一个逻辑规则,而是提供一定封装性的实用编程规则。泛化关系支持可替代性。
可替代性规则使得孩子不能去掉或者放弃它的父亲的属性。否则,孩子就不能在其父亲被声明的地方替代。
333.子系统(subsystem)
作为一个整体处理的元素构成的包,包括把包的内容作为一个内在整体处理时对其行为的声明。子系统被模型化为包和类。子系统有一个接口的集合,这些接口描述了它与系统的其他部分的联系以及在何种情况下可以使用它们。
见 接口(interface)、包(package)、实现(realization)。
语义
子系统是系统的一个内在的可以被作为一个抽象独立单元的部分。它代表系统的某一部分自然发生的行为。作为一个整体,它有它自己的行为声明和实现部分。它的行为声明定义了作为一个整体,它和别的子系统交互的自然发生的行为。它的行为声明以用例和别的行为元素的形式给出。实现部分以构成其内容的附属元素的形式描述了行为的实现,并且作为被包含元素之间的合作集合给出。
系统本身构成最顶层的子系统。一个子系统的实现可以写成一些低级子系统的合作。在这种方式下,整个系统可以扩展为子系统的层次树,直到最底层的子系统以普通类的形式定义给出。
子系统可能包含结构元素和声明元素,比如由子系统导出的用例和操作。子系统声明通过结构元素实现。子系统的行为实际上是它里面的元素的行为。
结构
对一个子系统的声明包含指定为声明元素的元素,连同作为一个整体定义在子系统上的操作和接口。声明元素包括用例,限制,用例之间的联系等等。这些元素和操作定义了子系统作为自然实体所执行的行为,即它的各个部分协同工作的净结果。用例声明了子系统和外部操作者的完整的交互顺序。声明没有说明各个部分是如何交互以完成必要的行为。
子系统里的其他元素实现它的行为。这可能包含各种类元和它们之间的联系。子系统联系元素之间的合作构成的集合实现这个声明。通常,由一个或者多个合作实现每个用例。每个合作描述实现元素的实例如何合作完成用例或操作声明的行为。在声明级别的所有到达和出发的消息都必须被映射为它的实现元素以及其他子系统的实现元素之间的消息。
子系统是一个包,具有包的特性。特别的,子系统的导入象对包的描述那样工作,子系统之间的泛化具有和它的内容相同的可见性。
表示法
子系统表示为在子系统名称上面的包含关键字 subsystem 的包符号(带有小标签的矩形)。如图13-175。
讨论
考虑一个子系统,它处在设计元素的自然分组里,比如逻辑类。一个组件是一个实现元素的构成的自然分组,例如实现级别的类。在很多情况下,子系统作为组件实现。这简化了从设计到实现的映射。所以,这是一种普通的结构方法。更进一步,很多组件实现为直接实现组件接口的的类。在这种情况下,子系统、组件、类可以有完全相同的接口。
334.子类型(subtype)
是其他类型的孩子的类型。更中性的术语"孩子"可以用于任何可泛化元素。
见泛化(generalization)。