第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放在对象停止存在的那一点,即,或者放在表示销毁对象的消息的箭头的头部,或者放在对象自己销毁的那一点。对于对象活动的任何阶段,生命线加粗到两倍的实心线。这包括主动对象的整个生命和被动对象的激活--对象的某个操作执行的阶段,包括这个操作等待它所调用的操作返回的时间。如果这个操作直接活间接的递归调用它自己,另一条两倍实心线覆盖在它上面以表示双重激活(可以是多于两个)。对象的相对次序没有重要意义,尽管合理的安排它们以使消息箭头所覆盖的距离最小是有帮助的。对激活的注释可以放在附近的空白处。
每个消息显示为一个从发送消息的
|