1.可见性修饰符
面向对象思想中有一个重要概念是封装,封装意味着对象中成员的“可见性”是不同的。这里的对象通常指类和包,而它们的可见性通过可见性修饰符进行定义。
在UML中,类对象成员的可见性修饰符有四种,其具体说明如下:
修饰符 | 可见性 | 说明 |
---|---|---|
+ | 公共 | 成员属性、行为、值能够被任何对象看到、引用或调用 |
- | 私有 | 成员仅能在当前类的实例内被访问 |
# | 保护 | 成员在当前类及其派生类的实例内被访问 |
~ | 包 | 同一包内的同级元素可访问 |
类成员的前三种可见性比较容易理解,它与面向对象的编程语言中的含义是一致的,而最后的“包可见性”要结合包进行理解,可以结合以下包的可见性描述一并进行理解。
包与可见性有着密不可分的关系,与包相关的可见性说明如下:
修饰符 | 可见性 | 说明 |
---|---|---|
+ | 公共 | 具有公共可见性的成员对所有可以访问当前包的内容的元素都是可见的 |
- | 私有 | 具有私有可见性的成员仅在当前包内可见 |
# | 保护 | 具有受保护可见性的成员对与拥有它的命名空间具有泛化关系的元素是可见的(在继承元素中可见)。不允许在包或由包直接拥有的元素上使用。包不支持泛化/专门化。 |
~ | 包 | 具有包可见性的成员在其最近的封闭包(假设任何中间拥有元素具有适当的可见性)内部是可见的。在其最近的封闭包或标记为具有包可见性的元素之外是不可见的。不允许在包或包直接拥有的元素上使用。 |
上述说明文字比较晦涩,难于理解,下面我们通过两个示例来进行理解。
先看下图示例,包pkg1包含有三个元素:公共类Book、私有对象myBook及子包pkg2。它们的可见性分别被定义为公共、私有和公共。也就是说在包pkg1之外,可以看到类Book和包pkg2,而看不到对象myBook,但在包pkg1之内,这三个元素相互之间则都是可见的。
而在实际应用中,情况远比上述示例复杂,假想有如下图所示的包pkg1及子包pkg2,它们又各自包含了一些元素,这些元素之间的可见性又是什么样的呢?
为了方便起见,我们用以下表格来说明各元素之间的可见性。表格第一行是主动尝试去看其他元素的发起者,表格第一列是被看元素,在表格中我们则分别用Y和N分别表示被看元素相对主动元素的可见或不可见。
主动元素/被看元素 | C1 | C2 | C3 | C4 |
---|---|---|---|---|
-C1 | - | N | N | N |
-C2 | N | - | Y | Y |
+C3 | Y | Y | - | Y |
C4 | N | N | N | - |
+a1 | Y | Y | Y | Y |
-a2 | N | N | Y | N |
~a3 | N | Y | Y | Y |
#a4 | N | N | Y | Y |
对位于包pkg1中的C1,它只能访问包pkg2中的公共元素,因而它可以看见类C3;同时由于C3的属性a1是公共的,因此C1也可以访问到a1。
对位于包pkg2中的类C2,由于C1是包pkg1中的私有元素,因而它看不到;C3是同一个包中的公共元素,所以对C2可见,类似地,C3的公共属性a1对C2也是可见的;C3的属性a3的可见性被声明为包可见,故而它对同一包中的C2是可见的;同一个包中的C4由于未定义可见性,因而它对C2不可见。
对位于包pkg2中的类C3,同样看不到pkg1中的私有元素C1;而同在包pkg2中的C2对它可见;属性a1、a2、a3、a4均是类C3的成员,故均对C3可见。
对位于包pkg2中的类C4,其他元素对它的可见性与对C2的可见性类似;不过由于C4是C3的继承类,因而C4还可以看见C3中的保护成员a3。
2.修改保护/只读(readOnly & query)
对象的属性除了可见性外,在有些场景下,它还需要具备防止被修改的特性。例如很多系统中都会存在有“用户”对象,在定义代表用户的类user时,它会包含有一些属性,其中代表用户姓名的name、出生日期的birthdate等属性,很显然在对象实例中这些属性一旦被赋值,就应当不允许再改变。为了达到此目的,通常可以将这些属性的可见性设定为私有的,但在对象内,依然可以改变这些属性的值,更有效的方法是在属性名后添加“{readOnly}”,它表示该属性一旦被赋值,将不可改变。
类似地,类的操作其实也可从是否修改属性值的角度分为两类,其中一类是仅通过对属性的计算返回一个值而不会去改变任何属性的值,这样的操作在UML中称作查询,这也可以通过在操作后添加“{query}”进行标示。
以下是一个使用{readOnly}和{query}的示例。
3.参数方向(in/out/inout/return)
操作参数可以指定它的传递方向,它有如下4个可能的修饰符:
In:参数值由调用者传入,操作结束后其值不变
Inout:参数值由调用者传入,再由操作传出,操作结束后其值可能发生变化
Out:参数值由操作传出
Return:作为返回值传递给调用者
上述说明应当比较容易理解,以下是使用这些符号的一个示例:
billcc( in CCN:String,
in owner:NameType,
inout debit:Currentcy,
out successFlag:Boolean
) return ReturnType
上述操作的前两个参数CNN和owner被指定为in参数,即它们在调用操作时应当被赋值且该操作不会改变它们的值;第三个参数debit被指定为inout参数,即在调用操作前它应当被赋值,调用操作后它的值可能被改变;第四个参数successFlag被指定为out参数,即调用操作前它的值无关紧要,操作不会使用它,但操作会在执行过程中设定它的值,并且在操作完成后可以取得其值。
上述操作也指定了返回值类型为ReturnType,它可用于表明操作失败的原因,例如没有发现卡号、I/O或者网络错误等等。需要说明的是,在面向对象的分析与设计中,这类错误更合适的做法是通过抛出异常进行处理。
参考文献:
1.《OCUP 2 Certification Guide_ Preparing for the OMG Certified UML 2.5 Professional 2 Foundation Exam》 Michael Jesse Chonoles
2.《OMG® Unified Modeling Language® (OMG UML®) Version 2.5.1》