指南:设计类
主题
设计类代表系统实施中的一个或多个类的抽象;其所对应的准确内容依赖于实施语言。例如,在诸如 C++ 的面向对象的语言中,类可以对应于一个普通的类。或在 Ada 中,类可以对应于包的可见部分中定义的已标记类型。
类定义对象,对象然后实现(实施)用例。类是从用例实现对系统中所需对象所作出的需求中以及从任何先前已开发的对象模型中产生的。
类的好坏很大程度上依赖于实施环境。类及其对象的适当大小依赖于编程语言,例如在使用 Ada 时认为是正确的内容在使用 Smalltalk 时就可能是错误的。类应映射到实施语言中的特定的特殊现象上,并且应构造类以便映射产生良好的代码。
即使实施语言的特性影响了设计模型,您也必须保持类结构易于理解和修改。设计时就好象已具有了类和封装,即使实施语言不支持这一点也是如此。
其它对象访问或影响对象的属性或关系的唯一方法是通过其操作。对象的类定义了对象的操作。通过操作可以执行一个特定的行为,该行为可能影响对象保留的属性和关系并引起执行其它操作。在 C++ 中一个操作对应于一个成员函数,而在 Ada 中对应于一个函数或过程。分配给对象的行为依赖于该对象在用例实现中的角色。
在操作规范中,参数组成形式参数。每个参数都有一个名称和类型。可以使用实施语言语法和语义来指定各操作以及它们的参数,以便在开始编码时,已在实施语言中指定了它们。
示例:
在回收机器系统中,Receipt Basis 类的对象跟踪客户交来的某种类型的堆积物项的数量。Receipt Basis 对象的行为包括增加返回的对象的数量。操作 insertItem 接收对交来项的引用,以实现此目的。

指定操作时使用实施语言语法和语义。
操作几乎总是表示对象行为。操作还可以表示类的行为,在此情况中它是类操作。可以在 UML 中通过规定操作类型范围对此建模。
可以在操作上实现以下可见性:
- 公用:该操作对类自身之外的模型元素都可见。
- 受保护:该操作仅对类自身、其子类或其 friends 类可见(依赖于语言)
- 私有:该操作仅对类自身及其 friends 类可见
- 实施:该操作仅在类自身中可见。
应尽量少地使用公用可见性,仅在另一个类需要该操作时使用。
受保护可见性应作为缺省值;它保护该操作不被外部类使用,促进了松耦合和行为封装。
私有可见性应在为防止子类继承该操作的情况中使用。这提供了将子类从超类分离的方法,并减少了删除或排除未使用的继承操作的必要。
实施可见性是最受限制的;在只有类自身可以使用该操作的情况中使用它。它是“私有”可见性的变体,“私有”可见性适合大多数情况。
对象可以根据其所处的状态,对特定消息作出不同的反应;关联状态表图定义了对象的依赖于状态的行为。对于对象可以进入的每个状态,状态表图描述它可以接收的消息、可以执行的操作以及之后该对象所处的状态。请参考指南:状态表图以获取更多信息。
协作是一组动态的对象交互作用,在这些交互中一组对象通过彼此发送消息进行通信。在 Smalltalk 中发送消息非常直截了当;而在 Ada 中是作为子程序调用来完成的。消息发送到接收对象,接收对象调用该对象中的操作。消息指明了要执行的操作名称和所需参数。当消息发送时,将为所有参数提供实际参数(形式参数的值)。
在交互图中描述了用例实现中对象之间的消息传输以及调用操作后对象遵循的控制重点。请参阅指南:序列图和指南:通信图以获取关于这些图的信息。
属性是对象的一个命名的特征。属性名称是一个名词,它描述了与对象相关的该属性的角色。当创建对象时,属性可以有一个初始值。
仅当对属性建模可以使对象更易于理解时,才应这样做。仅当对象的特性是该对象本身的特性时,才应将对象特征建模为属性。否则,应使用到类(该类的对象表示了该特性)的关联或聚集关系来对特性建模。
示例:

如何对属性建模的示例。家庭的每个成员都有姓名和地址。这里,已标识属性我的姓名和家庭地址,其类型分别为姓名和地址:

在该示例中,使用关联而非属性。我的姓名特征可能对家庭中的每个成员都是唯一的。因此将它建模为属性类型为姓名的属性。而地址由所有家庭成员共享,所以最好用家庭成员类和地址类之间的关联对它进行建模。
并不总是很容易立即决定是将某概念建模为单独的对象,还是建模为另一个对象的属性。在对象模型中有不必要的对象将导致不必要的文档和开发开销。因此必须建立某些条件以确定某概念对系统的重要程度。
- 可访问性。用于控制对象与属性选择的不是真实生活中概念的重要性,而是在用例期间访问它的必要性。如果经常访问该单元,则将它建模为对象。
- 执行期间的分离。将用例执行期间独立处理的概念建模为对象。
- 与其它概念的联系。将与某些其它概念严格相关且从不单独使用而总是通过对象使用的概念建模为该对象的属性。
- 来自关系的需求。如果由于某个原因,必须从两个方向上关联单元,则重新检查该单元以查看它是否应当是单独的对象。两个对象不能关联一个属性类型的相同实例。
- 发生频率。如果一个单元仅在用例期间存在,则不要将它建模为对象。而是将它建模为执行所讨论行为的对象的一个属性,或只是在受影响的对象的描述中提到它。
- 复杂性。如果对象由于其属性而变得过于复杂,可以将其中的一些属性抽取到单独的对象中。然而,请适度执行此操作,以防具有过多的对象。另一方面,各单元可以非常直截了当。例如,归类为属性的单元是那些 (1) 足够简单,从而可以直接受实施语言中的基本类型的支持,例如 C++ 中的整数;(2) 足够简单,从而可以使用实施环境的独立于应用程序的组件来实施,例如 C++ 和 Smalltalk-80 中的字符串。
对不同的系统,可能以不同的方式对某概念建模。在一个系统中,某个概念可能非常重要,应将它建模为对象。在另一个系统中,它可能只是次要的,应将它建模为对象的属性。
示例:
例如,对于航空公司,将开发一套支持出发地的系统。

支持出发地的系统。假设机场的工作人员需要支持出发地的系统。对于每个出发地,您必须定义出发时间、航线和目的地。可以将此内容建模为类出发地的对象,该类具有属性出发时间、航线和目的地。
但如果是为旅行社开发该系统,则情况可能会有所不同。

航班目的地组成其自己的对象目的地。
当然仍需要出发时间、航线和目的地。但还有其它需求,因为旅行社感兴趣的是对特定目的地查找出发地。因此必须为目的地创建一个单独的对象。当然出发地和目的地对象必须彼此知道对方,可以通过这两个类之间的关联来实现。
某些概念的重要性的观点对确定应在类中定义哪些属性也是有效的。如果类汽车的对象是机车登记系统的一部分,与该类对象是汽车制造系统的一部分相比,无疑应定义不同的属性。
最后,将什么表示为对象以及将什么表示为属性的规则不是绝对的。理论上,可将任何事物建模为对象,但这是很麻烦的。一个属于经验之谈的简单规则是将对象看成在某些场合以与其它对象无关的方式来使用的某种东西。另外,不需要使用属性对每个对象特性建模,仅对理解对象所需的特性建模即可。不应对过分特定于实施的详细信息建模,最好由实施者处理它们。
类属性 
一个属性几乎总是表示对象特性。属性还可以表示类特性,在此情况中它是一个类属性。可以在 UML 中通过规定属性类型范围对此建模。
对象可以封装某些无须对象执行任何行为就可更改其值的内容。这些内容可能实际上是外部单元,但并没有被建模为一个参与者。例如,可能已选择系统边界,以便某种形式的传感器设备放置在它们中间。然后可以将传感器封装在对象中,以便它评估的值组成属性。然后该值可以连续地或以一定时间间隔更改,而系统中的任何其它对象不会影响该对象。
示例:
可以将温度计建模为一个对象;该对象具有表示温度的属性,并且作为对环境温度更改的响应更改值。其它对象可能通过对温度计对象执行操作,请求当前温度。

温度计对象中的属性温度的值自发更改。
仍可以将以此方法更改的封装值建模为一般属性,但应在对象类中描述它是自发更改的。
属性可见性假设以下值之一:
- 公用:在包含该类的包的内外都可以看到该属性。
- 受保护:该属性仅对类自身、其子类或其 friends 类可见(依赖于语言)
- 私有:该属性仅对类自身及其 friends 类可见
- 实施:该属性仅对类自身可见。
应尽量少地使用公用可见性,仅在另一个类可以直接访问该属性时使用。定义公用可见性实际上是将属性可见性定义为受保护、私有或实施的快速表示法,具有相关联的公用操作来获取和设置属性值。可以将公用属性可见性可用作对代码生成器的声明:应自动生成这些 get/set 操作,在类定义期间可省时。
受保护可见性应作为缺省值;它保护该属性不被外部类使用,提供了松耦合和行为封装。
私有可见性应在为防止子类继承该属性的情况中使用。这提供了将子类从超类分离的方法,并减少了删除或排除未使用的继承属性的必要。
实施可见性是最受限制的;在只有类自身可以使用该属性的情况中使用它。它是“私有”可见性的变体,“私有”可见性适合大多数情况。
某些类可能表示复杂抽象并具有复杂结构。对类建模时,设计者希望表示其内部参与元素以及它们的关系,以确保实施者将相应地实施在该类中发生的协作。
在 UML 2.0 中,类被定义为结构化类,能够拥有内部结构和端口。然后,可以将类分解为已连接的部件的集合,之后还可以进一步分解这些部件。可以通过强制来自外部的通信通过遵守已声明接口的端口来封装类。
因此,除了使用类图来表示类关系(例如关联、组装和聚集)以及属性之外,设计者可能希望使用组合结构图。该图为设计者提供了一种机制,以显示内部部件的实例是如何在给定类的实例中扮演它们的角色的。 关于该主题的更多信息以及组合结构图的示例,请参阅概念:结构化类。
|