560 likes | 711 Views
第四部分 结构建模. 第十章类的详解 1 、引言 随着软件设计和建造的深入,往往需要有更加完备的结构模型,以更详细地反映软件系统的细节. 例如,当建模的目的是为了软件系统的建造时,软件模型表达的内容必须足够完备,直至能够支持通过模型的正向工程进行软件的建造。 本章将更深入地讨论类的完备模型应具备的 UML 建模元素。. 1. 引言. 图 5.3 一个完备的类图. 2. 可见性. 类的存在价值在于它的对象要参与软件系统的交互, 孤立存在的类是没有存在价值的。 类的基本构成包括属性和操作。 当类的对象参与交互时 类的属性
E N D
第四部分 结构建模 第十章类的详解 1、引言 • 随着软件设计和建造的深入,往往需要有更加完备的结构模型,以更详细地反映软件系统的细节. • 例如,当建模的目的是为了软件系统的建造时,软件模型表达的内容必须足够完备,直至能够支持通过模型的正向工程进行软件的建造。 • 本章将更深入地讨论类的完备模型应具备的UML建模元素。
1.引言 图5.3 一个完备的类图
2.可见性 • 类的存在价值在于它的对象要参与软件系统的交互, • 孤立存在的类是没有存在价值的。 • 类的基本构成包括属性和操作。 • 当类的对象参与交互时 • 类的属性 • .定义了类的结构和其对象在交互时所处的状态 • 类的操作 • .定义了类的对象在交互中为其它对象提供的服务。
2.可见性 • 当某一类的对象和其它类的对象进行交互时, • 它的某些操作是可以被其它对象通过消息直接启动的, • 通过启动操作,目标对象为其它对象提供了服务 • 而有些操作是不能被其它对象直接启动的, • .它们的作用只是为对象的服务的实现提供支持。 • 类的属性也是一样, • 有些属性代表的状态可以被其它对象存取, • 而有些属性代表的是对象的内部状态,它们只能被类的操作所存取。 • 类的构成(property)的能被其它类的对象所访问的特性是由类的可见性规定的。 • 在大多面向对象的程序设计语言里,都有规定类的属性和操作的可见性的规则。 • UML的类可见性就是为程序设计语言的对应特性进行建模的手段。
2.可见性 • 可见性的定义 • UML里,类的可见性就是规定类的基本构成能否被其它的类所引用的规则。 • 类的可见性可分为三类:公有的,保护的,私有的 • 如果一个类的构成具有公有可见性( public),那么这个构成可以被任何别的类访问。 • 如果一个类的构成具有保护可见性(protacted),则代表此构成可以被此类的导出类访问。 • 如果一个类的构成具有私有可见性(private),那么代表此构成只能被此类本身的操作访问。 • 类的可见性的图形表示 • 如果类的构成具有公有可见性,则在构成的名字前缀以“+”: • 如果类的成具有保护可见性,则在此构成的名字前面缀以“#”: • 如果类的构成具有私有可见性,则在此构成的名字前面缀以“-”. • (图10.1)
2.可见性 图10.1 可见性
2.可见性 • 模型包的可见性:仅可以被定义在同一模型包内的类访问。 • 关键字:package • 图形:
3.作用域 • 类是对象的抽象。对象是类的坚实存在。 • 类由属性和操作组成,属性和操作统称为类的构成。 • 类可以有多个对象。 • 通常,当一个类的多个对象同时存在时, • 此类的同名的构成具有多个副本。 • 这意味着 • .修改一个对象的属性的取值不会对另一个对象的同名属性发生作用; • .一个对象的操作的执行不会影响同一个类的另一个对象的状态。 • 但在有些情况下, • 可能需要同一个类的各个对象能共享一个或多个属性。 • .例如,可能需要设定一个变量来统计某一类当前存在的对象的数目。 • 类的构成的作用范围,在UML里,称为作用域(scope)
3.作用域 • 类的构成的作用域规定了类的构成是定义在每个对象上的还是定义在整个类的范围内的。 • 类的构成的作用域共有两种,即对象作用域和类作用域。 • 对象(instance) 作用域指的是此构成对此类的每个对象都有一个副本。 • 类(static)作用域指的是此构成只有一个副本,它能为类的所有对象共享。 • 如果一个操作具有类作用域,则意味着此操作的结果对整个类有效( ->操作只涉及静态属性) • 对C++而言,具有类作用域的类的构成,相当于是静态(static)变量和静态函数 • (在C++里,如果一个函数是静态的,就意味着此函数只能访问静态变量)
3.作用域 • 作用域的图形表示 • 如果类的构成的名字下面加了下划线,就表示此构成具有类作用域, • 否则就是具有对象作用域。 • 在有些工具中作用域的表示有些微小区别, • 例如,图10.2是ROSE里作用域的图形表示,其中, • .符号“$’’代表类作用域。
3.标准扩充 图10.2 类的作用域
4.抽象类和多态性 • 利用泛化关系,可以指定一系列具有共享的结构和行为的类。 • 这些类具有共有的结构, • 在交互中通过相同的接口提供不同的服务。 • 基于图形用户界面的交互式软件的工具栏就是这样的一个例子(图5.3) • .工具拦里的各鼠标工具都接受相同类型的鼠标消息 • 但对鼠标消息的解释则随鼠标工具的功能的不同而不同。 • 在考虑鼠标工具的实现机制的时候,通常是把各工具都应实现的鼠标消息的处理操作提取出来,在一个基类里为它们声明调用格式, • 把各工具的实现类声明为此基类的导出类。 • 在这种情形下,基类只为其导出类定义了公共的结构和操作接口,它本身不会被实例化为对象。 • 在UML里,可以用抽象类为它们建模。
4.抽象类和多态性 • 抽象类的定义: • 抽象类在UML里被定义为没有直接实例的类。 • 抽象类的图形表示: • 为了将抽象类区别于其它类,在图形表示上,把抽象类的名字用斜体字拼写 (图10.4) • 抽象类里的操作的实现方法( method)都可以是未定义的, • 它们的实现将在各导出类里定义。 • 这样的操作被称为是抽象操作( abstract operation)。 • 抽象操作的定义 • 在UML里,抽象操作被定义为是未指定实现方法的操作。 • 其操作的实现方法必须在其后继(导出类)中定义。 • 如果把抽象操作映射到C++, • 则抽象操作等同于C++里的纯虚函数。 • 抽象操作的图形表示 • 在类图上,如果一个操作的名字是用斜体字拼写的,那么,这个操作就是抽象操作。
4.抽象类和多态性 图10.4 抽象类和多态性
4.抽象类和多态性 • 使用抽象类、抽象操作以及泛化关系来描述一系列的类的继承关系后,在抽象类里定义的操作在交互里的动态行为将取决于参与交互的对象的类型。 • 即,根据此对象实例化的那个抽象类的导出类的对应操作的实现方法,决定此操作在交互时的动态行为。 • 多态性 • 在UML里,多态性指的是在泛化关系的层次结构中,处于不同层次的导出类的署名(signature)相同的操作,可以有不同的动态行为。 • 如果对应到C++,则多态性就对应于C++的如下特性, • 即:类型为指向抽象类的指针的变量所代表的对象的动态行为将取决于此对象在运行时所实例化的类的实现。 • 在UML里,操作和操作的实现是两个不同的概念。 • 操作定义了一个类能为其它类能提供的服务。但它不规定此服务的实现方法。 • 操作的实现在UML里被称为实现方法(method)。 • 因此,抽象操作是没有定义实现方法的操作。 • 而 利用了操作的多态性的类的继承网络里, • 具有相同署名的操作,可以有多个实现方法。
4.抽象类和多态性 • 在一个类的模型图上,将存在各种具有不同特性的类。 • 例如:在一个表达类的多重继承的类图上,必定存在一个或多个类,它们处于类的继承层次结构的顶层,即它们不是从任何基类导出的。 • 有时,出于设计的需要,也存在一些类,它们不会是,或规定不得是任何类的基类。 • 为了强调这样的类,UML里定义了叶子类(leaf class)、根类(root)以及坚实类(concrete class)的概念。 • 叶子类是没有任何导出类的类。 • 图形表示: • .被表示为对类的约束,使用关键字leaf对类的名字进行修饰, • 把leaf用花括弧扩起来({1eaf))放置在类的名字的下方 (图10.4)。 • 根类是没有任何基类的类。 • 也被表示为对类的约束,关键字为root,它(root)被放置在类的名字的下方 • 坚实类在UML里被定义为是可以被直接实例化的类。 • 我们建模时遇到的大多数的类都是坚实类,UML不对坚实类做特殊的图形标注。
4.抽象类和多态性 • 抽象操作是实现方法由类的导出类定义的操作。与此相对应,UML也定义了实现方法必须被定义的操作,这就是叶子操作(leaf operation), • 叶子操作又称为坚实操作(concrete operation) • 叶子操作被定义为是不具备多态性且不能被重载的操作。 • 被重载指的是它的实现方法可以在其所在的类的导出类里被定义。 • .因此一个操作如果不得被重载,就意味着此操作本身必须被指定实现方法。
5.类的重复度 • 在对软件系统进行逻辑设计时,在某些情形之下,可能需要限制类的对象在软件系统中存在的数目。 • 出于交流的目的,可能需要在逻辑视图上强调这种情形,这时就可以用类的重复度(multiplicity)来表达。 • 重复度的定义 • 在UML里,对类的可同时存在的对象的数目的限制,称为类的重复度。 • 重复度描述的是一个数目范围。数目范围用一个表达式描述。下表就是此表达式可以采取的形式的几个例子:
5.类的重复度 • 重复度的图形表示 • 在类图上,类的重复度表达式被放置在类的图标的右上角(图10.5) • 如果类的重复度表达式在类图上被省略,那么此类的重复度缺省为n • 即对此类的可同时存在的对象的数目没有限制。 • 重复度同样可以应用于类的属性, • 这时,按照UML的规定,可以把重复度表达式用方括号扩起来,放置在对应的属性名字的后面。 • 如果属性的重复度表达式省略,则属性的重复度缺省为1 • (在某些工具如ROSE,属性的重复度被放置在属性的类型说明的后面,如图10.5所示)
5.类的重复度 图10.5 类和属性的重复度
6.属性的语法 • 类的属性的构成包括: • 属性名字、 • 属性的可见性、 • 属性的类型以及 • 属性的重复度。 • 为了构造一个完整的类的模型,还需要为属性指定其它的一些构成,它们是: • 属性的初始值、 • 属性的作用域和 • 属性的可变性( changeability)。 • UML类的属性的完整的语法可以表述如下: • [可见性]名字[重复度] [:类型][=初始值][特性串] • [可见性]名字[:类型][重复度][=初始值][{特性串)] • 在上面的式子中,用方括号括起来的构成是可省略的。 • 这意味着在建模时,依据模型图所表达的内容的侧重面,属性的构成可以被隐藏,甚至可以暂时缺省。
6.属性的语法 • “特性串”用于描述属性的可变性。 • 可变性描述了对属性取值的修改的限制。 • 共有三种预定义的可变性,它们是: • <1>、changeable(可变的) : • 表示此属性的取值没有限制,它可以被随意修改。 • <2>- addOnly(只可加): • 只对重复度大于1的属性有效。 • 此属性的每个实例在被初始化或赋值之前,其取值是无效的,随着交互的进行,属性的这些实例被逐步地初始化或赋值,这些被初始化的实例的取值才是有效的。 • addOnly特性串表明,对这个属性而言,新的有效值只能添加到此属性的有效值集合中去, • 一旦有效值被加入到此属性的有效值集合中,就不能被更改或删除。属性取有效值的数目只可增加。 • <3>、frozen(冻结的):frozen/readonly特性串表明属性所在的类的对象一旦被初始化,它的取值就不能被再改变。这相当于C++里的常量(const)。 • 例如:如果一个类的属性具有如下的形式, • id:Interger frozen • 就表示此属性的取值在对象被创建之后是不可更改的。
7.操作的语法 • 操作说明的最后一部分是特性串。 • 特性串是一个UML约束,它对操作的语义进行附加的修饰。 • 除了前面提到的“叶子操作”外,还可以使用下面的特性约束: • isQuery (query):它表明被修饰的操作是一个用于查询用的操作,此操作执行结束之后不会改变类的状态。 • Sequential:表明被修饰的操作在其对象内不能同时有两个以上的控制流。而这一点必须由操作的调用者来保证,对象本身不存在禁止两个控制流被同时启动的机制。如果此操作的两个或两各以上的控制流在对象的交互中被启动,则此操作的语义和完整性将不被保证。 • guarded:调用者可以同时启动此操作的多个控制流,但这些控制流被对象自身转换为顺序执行的控制流。者意味着当多个控制流被同时启动时,此操作的控制流或者正常执行,或者被自我阻塞,直至没有其它控制流在正常执行为止。 • concurrent(并行的) :操作可以有多个控制流被同时启动,且正常的控制流可以同时运行,但操作的语义和完整性仍可保证。
7.操作的语法 • 下面是合法的操作的写法的一些例子: • Set (n:Name, s: String) -- 它标明了操作的参数 • GetID():int -- 它标明了操作的名字和返回类型 • Restart(){guarded) -- 它标明了操作具有guarded特性
8.模板类 • 在考虑软件系统的实现时,有时需要使一个类能对多个其它不同的类施加相同的运算。 • 例如:在能够处理多种图元的图形设计工具中,在用户和软件交互时,当用户在任意图元上双击鼠标时,系统应弹出一个属性对话框,以使用户能了解和更改此图元的属性。 • 对于不同的图元,它的属性信息是不同的,因此弹出的对话框也不一样。 • 但对于处理鼠标在显示窗口上的操作的对象而言,它发出的启动对此图元的属性对话框的消息的格式却是相同的。 • . 因此,在处理窗口鼠标消息的对象中,可以对被操作的图元对象的类型不做区别,只是对此对象发出一个弹出属性对话框的消息。 • 为了支持这样的运行机制,需要 • 在定义窗口鼠标操作的对象的类里,定义一个形式化的类, • 此类就象是函数的形式参数一样, • 只有在提供了实际参数的时候,才能决定此类的实际内容。
8.模板类 • 在许多程序设计语言里,提供了这种在类中以形式参数来代表其中引用的其它类的机制。 • 在UML中,这种机制是用模版类(template class)来建模的。 • 定义: • 在UML中,模版类是一个参数化的类。 • 模版类和实际类( actual class)的区别在于 • 模版类定义了一系列的形式参数。 • ·形式参数代表特定的类、对象和值,它们可以在模版类中被引用,而且其作用域只局限在模版类中。 • 在模版类的操作中,通过引用此形式参数的符号来定义对形式参数的处理。 • 模版类本身不能被直接使用, • 只有在为其中的形式参数指定了实际参数以后,才构成了一个能在软件系统中使用的类。 • 因此,一个模版类可以构造一系列不同的类, • 这只需为模版类指定不同的参数就可实现。
8.模板类 • (在有些建模工具中,模版类又被称为参数化的类( parameterized class)) • 在UML中,模版类的形式参数和实际参数的的结合,称为绑定( bind) • 通过绑定,用模版类定义具有明确语义的实际类的过程,则称为模版类的实例化( instantiate) • 由模版类通过实例化产生的类,称为实例化的类( boundclass,或instantiated class)
8.模板类 • 模板类的图形表示(图10.5) • 在UML的模型图中, 如果在一个标准的类图的右上角绘制一个虚线矩形, 就代表此类是一个模版类 • 在虚线矩形内, 是模版类形式参数的列表, • 形式参数表由一个或多个形式参数构成, • 形式参数之间用逗号分隔。 • 每个形式参数具有如下格式: name:type • 其中, name代表形式参数的名字。 冒号后面的type是此形式参数的类型. • type可以在参数表中被省略, -- 如果它被省略, 那么此形式参数的类型是类, -- 即相当于是将此参数定义name:class。 -- 这也是最常用 的情形。 -- 在这种情形下, 在对模版类实 例化的时候, 可以用任意的类或 类型作为它的实际参数。 -- 类之外的类型(例如: int)则必 须明确指定。
8.模板类 • 如果需要在模型图上表达某个类是由模版类实例化而来的, • 可以用绑定关系来把模版类和实例化的类联系起来。 • 绑定关系是依赖关系的一个变体,它用<<bind>>修饰,其后面是用括号括起来的绑定的实际参数列表。 • .绑定关系的箭头指向的类是模版类, • 箭头起始端是实例化的类。 • 另一种实例化的表达方式是隐式表达。 • 实例化的隐式表达不使用绑定关系, • 而是使用一个匿名的类, • 在此类的名字的位置标明了其模版类的名字和实际参数(图10.6,图10.6-1).
8.模板类 图10.6 模板类
8.模板类 图10.6-1 模板类-隐式表示
8.模板类 • 模版类、实例化的类及其绑定关系的UML模型可以通过正向工程,得到对应的程序设计语言代码。 • 下面就是对图10.6通过正向工程得到的C++源程序代码。 #define N9 9 class ClassB public: int j;}; class ClassA public: int i;}; template <class Item, class Value, int nClassID> class Map { public : Map () int bind (Item i, Value v) ; int ID; }; typedef Map< ClassA,ClassB,N9 > OrderMap; template <class Item, class Value, int nClassID> Map<ltem, Value, nClassID> : :Map ( ) {} template <class Item, class Value, int nClassID> int Map<Item,Value,nClassID>: :bind (Item i, Value v) {}
9.标准扩充 • UML的扩充机制同样适用于类。 • UML的扩充机制为软件系统的建模者提供了定义自己的建模元素的能力。 • 例如 • .可以用标记值对类进行修饰, • .可以用变体来定义具有特殊意义的类等。 • UML扩充机制除了给其使用者定义新的模型元素的自由度外,它本身也利用此扩充机制对其标准的建模元素进行了一些扩充。 • 这些扩充是UML里通过约束、变体和标记值等扩充机制预定义的一些关键字,它们被称为标准扩充(standard elements) • 在UML里,对类定义了四种标准扩充,它们是:
9.标准扩充 (1). metaclass(元类): • 元类( metaclass)是类的一个变体,表明此类的实例是类。 • 元类通常用来定义元模型(metamodel)。 • ·元模型是用来定义UML建模元素的模型。 • ·元类是定义类的类。 • 在模型图上,如果在类的名字的上方标以关键字《metaclass》,就代表此类是元类。
9.标准扩充 (2). powertype(超类型) : • 超类型是类的变体,表明此类是一个元类,而且其实例是一个指定类的所有导出类。 • 在一个类的继承关系网中(图10.7),考查一个基类 (tree)的所有导出类。 • .如果我们考虑在此模型的元模型中描述这些导出类, • 那么可以认为这些导出类是元模型中某一类型 • (类型(type) :类型也是类的变体,它定义了此类的对象的范围及可对这些对象施加的操作。它是程序设计语言中类型的抽象)的所有实例的集合, • 这类型就是超类型(图10.7)
9.标准扩充 图10.7 超类型
9.标准扩充 (3). stereotype(变体) : • 它是一个在元模型中定义变体的关键字。 • 它是元类的一个名为《stereotype》的变体,表明此元类的实例是类图上的一个建模元素的变体。 • 例如:图10.8是一个类图,但它描述的是定义类及其变体《actor》的元模型(即Class和Actor都是元类) • 在元模型上,类及其变体之间是继承的关系, • 因为从元类的意义上看,类的变体继承了类的结构(类的元类的结构包括属性和操作)。 • 为了强调actor是class的变体,就把actor的源类标以《stereotype》.
9.标准扩充 图10.8 变体的元模型
9.标准扩充 (4). class utility: • class utility是类的变体。它本身是一个类,但其操作和属性都具有类作用域。
10.类(class)和分类符(classifier) • 类是对软件系统建模时最经常用到的建模元素。 • 它抽象了软件系统的词汇, • 描述了这些词汇对应的事物的结构和行为。 • 类的一个重要的特征就是它的抽象/实例两分法(dichotomy) • 这意味着类是软件系统内的坚实对象的抽象, • 它描述了这些对象的公共的结构和行为。 • 类在软件系统中类被实例化为对象,存在于各种交互之中。 • 在UML里,类不是唯一的具有抽象/实例两分特性的建模元素。 • 其它具有抽象/实例两分特性的建模元素包括: • 接口、 • 数据类型、 • 信号、 • 部件、 • 结点、 • 子系统。
10.类(class)和分类符(classifier) • 分类符的定义 • 在UML,把具有结构和行为,且具有抽象/实例两分机制的建模元素称为分类符(classfier) • 或者说,分类符是UIIL里描述结构和特性的建模机制。 • 类是最重要也是最常用的分类符。 • 本章介绍的类的各种特性,例如:重复度、可见性、署名( signature)、多态性等,同样也适用于分类符。 • 并不是所有的建模元素都有抽象/实例的两分特性, • 例如,泛化关系和模型包就是纯粹概念化的, • 它们不会实例化为软件系统内的坚实存在, • 因此它们不是分类符。 • 下面是UML里的其它分类符:
10.类(class)和分类符(classifier) <1>、接口(interface): • 接口描述了类或部件的一组操作,这些操作实现了类或部件应提供的服务。 <2>、数据类型(datatype): • 数据类型是UML的一种类型(type),其取值是不带标识( identity)的。 • 数据类型对应于程序设计语言里特定类型的数值。 • .例如:整数和实数是两种数值类型,它们在程序里被直接表示为对应格式的数值,而没有抽象的表示 • 与此相反的是变量, • 它们是类型的实例,但不是数据类型,因为变量有标识。
10.类(class)和分类符(classifier) <3>、信号(signal): 信号是实例之间异步通信的描述。 <4>、部件(component): 部件是系统的物理的和可替代的一部分。它可为一个或多个接口提供实现。 <5>、结点(node): 结点是一个存在于运行时刻的物理存在,它代表一类计算资源,通常至少具有一定的存储能力及处理功能。 <6>、用例(use case): • 用例包含了一系列的行为,这些行为为软件的使用者提供了可衡量的价值。 • 用例的实例是协同。 <7>、子系统(subsystem): 子系统包括了一组模型元素,它为这些模型元素规定了作为一个整体应该具备的动态行为。
10.类(class)和分类符(classifier) • 上面这些分类符在UML里都有特定的图形标识以互相区别, • 视其表达的建模重点的不同, • 它们通常也出现在不同的模型图上。 • 例如, • 部件出现在部件图上, • 用例出现在用例图上, • 结点则出现在分布图上。 • 图10.9是这些分类符的图形表示的例子。
10.类(class)和分类符(classifier) 图10.9 分类符
11.建模指南 • 在使用类对软件系统进行建模时,应记住 • 类是软件系统中的词汇。 • 软件系统的词汇对应于软件系统内部坚实存在的一类具有相同结构和动态行为的对象。 • 在用类为系统建模时, • 首先应根据软件系统的动态行为的描述, • 确定系统内部应具备交互及参与交互的对象, • 根据这些对象的划分,抽象出系统的词汇一类。 • 然后,应该 • 为类指定其职责(responsibility), • 并进一步描述其语义(semantics)
11.建模指南 • 类的职责是类的对象在交互中应该履行的义务。 • 职责可以用非形式的文字串表示, • 它放置在用锚接到类的标注内。 • 为了强调此标注是类的职责,可以 • .把此标注修饰成名为《responsibility》的标注变体。 • 类的语义是类的结构和行为的形式说明。 • 语义应该用结构化的文字表达。 • 在模型图上,语义也可以被放置在标注内。 • 为了强调此标注是语义,可以把此标注修饰成名为《semantics》的标注变体。 • 除了使用结构化的文本之外,语义还可以用 • 交互图、 • 活动图、 • 状态图或 • 程序设计语言来表达。
11.建模指南 • 在描述了将类作为一个整体的职责和语义后, • 下一步需要为类的操作建模。 • 这包括为操作指定 • 入口条件(pre-c0nditi0n) • 出 口 条件(p0St-c0nditi0n), • 语义 • 实现方法(meth0d) • 实现方法可以用交互图或状态图定义。 • 入 口 条件 、 出 口 条件和语义可 以分别放置在变体名 为 《pre-c0nditi0n〉〉 , 《p0St-c0nditi0n〉〉 和 《SementicS〉〉 的 标注变体里 。 • 在某些工具里, 这些建模内容是通过操作的属腻话框设定的 -- 例如图10.10是ROSE的操作属性对话框, -- 可以通过它为类的操作指定语义和 入口 / 出口条件。
11.建模指南 图10.10 ROSE的操作属性设定对话框