3.6 基于类的建模
基于类的建模表示了系统操作的对象、应用于对象间能有效控制的操作(也称为方法或服务)、这些对象间(某种层级)的关系,以及已定义类之间的协作。基于类的分析模型的元素包括类和对象、属性、操作、类-职责-协作者(CRC)模型、协作图和包。
3.6.1 识别分析类
通过检查需求模型开发的使用场景,对系统开发的用例进行“语法解析”,可以进行类的识别。其中,每个名词或名词词组可以确定为类,并将这些名词输入到一个简单的表中,标注出同义词。如果要求某个类(名词)实现一个解决方案,那么这个类就是解决方案空间的一部分;否则,如果只要求某个类描述一个解决方案,那么这个类就是问题空间的一部分。分析类表现为如下方式之一。
●外部实体(如其他系统、设备和人员),产生或使用基于计算机系统的信息。
●事物(如报告显示、字母和信号),是问题信息域的一部分。
●偶发事件或事件(如所有权转移或完成机器人的一组动作),在系统操作环境内发生。
●角色(如经理、工程师和销售人员),由和系统交互的人员扮演。
●组织单元(如部门、组和团队),与某个应用系统相关。
●场地(如制造车间或码头),建立问题的环境和系统的整体功能。
●结构(如传感器、四轮交通工具和计算机),定义了对象的类或与对象相关的类。
此外,另一个重要的分类方法是指定义实体、边界和控制类。
在分析模型中,分析师可以考虑每个潜在类是否应该使用以下这些特征。
1)保留信息。只有记录潜在类的信息才能保证系统正常工作,这种分析过程中的潜在类是有用的。
2)所需服务。潜在类必须有一组可确认的操作,这组操作能用某种方式改变类的属性值。
3)多个属性。在需求分析过程中,焦点应在“主”信息上。事实上,只有一个属性的类可能在设计中有用,但是在分析活动阶段最好把它作为另一个类的某个属性。
4)公共属性。可以为潜在类定义一组属性,这些属性适用于类的所有实例。
5)公共操作。可以为潜在类定义一组操作,这些操作适用于类的所有实例。
6)必要需求。在问题空间中出现的外部实体,和任何系统解决方案运行时所必需的生产或消费信息,几乎都被定义为需求模型中的类。
分析建模的一部分重要工作是分类,也就是将分析模型的各种元素(如用例、分析类等)以一种方式分类,分组打包后称其为分析包,并取一个有代表性的名称。
3.6.2 描述属性
属性描述了已经包含在需求模型中的类,定义并澄清类在问题空间环境下的实际含义。例如,如果建立一个系统来跟踪职业棒球手的统计信息,类Player的属性与用于职业棒球手的养老系统中的属性是截然不同的。在前者,属性是与名字、位置、平均击球次数、担任防守百分比、从业年限及比赛次数等相关的;在后者,某些属性具有相同的含义,另外一些属性将被替换,例如,平均工资、享受优惠权后的信用、所选的养老计划和邮件地址等。
为了给分析类开发一个有意义的属性集合,软件工程师应该研究用例并选择那些合理“属于”类的“事物”。此外,每个类都应回答如下问题:什么数据项(组合项和/或者基本项)能够在当前问题环境内完整地定义这个类?
3.6.3 定义操作
操作定义了某个对象的行为。尽管存在很多类型的操作,但通常可以粗略地划分为4种类型:①以某种方式操作数据(如添加、删除、重新格式化和选择);②执行计算的操作;③请求某个对象的状态操作;④监视某个对象发生某个控制事件的操作。这些功能通过在属性和/或相关属性上的操作实现。因此,操作必须“理解”为类的属性和相关属性的性质。
在第一次迭代要导出一组分析类的操作时,可以再次研究处理叙述(或用例),并合理地选择属于该类的操作,为此可以再次进行语法解析并分离动词。这些动词中的一部分将是合法的操作,并能够很容易地连接到某个特定类。
另外,对于语法分析,分析师能通过考虑对象间所发生的通信获得对其他的操作更为深入的了解。对象通过传递信息与另一个对象通信。在继续对操作进行说明之前,探测到了更详实的信息。
3.6.4 类-职责-协作者建模
类-职责-协作者(Class-Responsibility-Collaborator,CRC)建模提供了一个简单方法,可以识别和组织与系统或产品需求相关的类。CRC模型实际上是表示类的标准索引卡片的集合。这些卡片分为3部分:顶部写类名,卡片主体左侧部分列出类的职责,右侧部分列出类的协作者。
事实上,CRC模型可以使用真的或虚拟的索引卡,意图是开发有组织表示的类。职责是和类相关的属性和操作。简单地说,职责就是“类所知道或能做的任何事”。协作者是提供完成某个职责所需要信息的类。通常,协作意味着信息请求或某个动作请求。
FloorPlan类的一个简单CRC索引卡如图3-13所示。CRC卡上所列出的职责只是初步的,可以添加或修改。在职责栏右边的Wall和Camera是需要协作的类。
1.类
类的分类可以通过如下分类方式进行扩展。
●实体类,也称为模型或业务类,是从问题说明中直接提取出来的(例如FloorPlan和Sensor)。这些类一般代表保存在数据库中和贯穿应用程序(除非被明确删除)的事物。
图3-13 CRC模型索引卡
●边界类,用于创建用户可见的和在使用软件时交互的接口(如交互屏幕或打印的报表)。实体类包含对用户来说很重要的信息,但是并不显示这些信息。设计边界类的职责是管理实体对象对用户的表示方式。例如,一个被称为CameraWindow的边界类负责显示SafeHome系统监视摄像机的输出。
●控制类自始至终管理着“工作单元”。也就是说,设计控制类可以管理:①实体类的创建或更新;②当边界类从实体对象获取信息后的实例化;③对象集合间的复杂通信;④对象间或用户和应用系统间交换数据的确认。通常直到设计开始时才开始考虑控制类。
2.职责
在给类分配职责时建议考虑以下5个指导原则。
1)智能系统应分布在所有类中以求最好地满足问题的需求。每个应用系统都包含一定程度的智能,也就是系统内所含有它知道的及所能完成的。智能在类中可以有多种分布方式。建模时可以把“不灵巧”类(几乎没有职责的类)作为一些“灵巧”类(有很多职责的类)的从属。尽管该方法使得系统中的控制流简单易懂,但缺点是:把所有的智能集中在少数类,使得变更更为困难;将会需要更多的类,因此需要更多的开发工作。
如果智能系统更平均地分布在应用系统的所有类中,每个对象只了解和执行一些事情(通常是适度集中),并提高系统的内聚性,这将提高软件的可维护性,并减少变更的副作用影响。
为了确定是否恰当地分布了智能系统,应该评估每个CRC模型索引卡上标记的职责,以确定某个类是否应该具有超长的职责列表。出现这种情况就表明智能太集中(可能需要将一个类分成多个类或子系统,以便更有效地分布智能)。此外,每个类的职责应表现在同一抽象层上。例如在聚合类CheckingAccount操作列表中,评审人员注意到两项职责:账户余额和已结算的支票。第一个操作的职责意味着复杂的算术和逻辑过程;第二个操作的职责是指简单的办事员活动。既然这两个操作不是相同的抽象级别,已结算的支票应该被放在CheckEntry的职责中,这是由聚合类CheckingAccount压缩得到的一个类。
2)每个职责的说明应尽可能具有普遍性。这条指导原则意味着应在类的层级结构的上层保持职责(属性和操作)的通用性(因为它们更有一般性,将适用于所有的子类)。
3)信息和与之相关的行为应放在同一个类中。这实现了面向对象原则中的封装,数据和操作数据的处理应包装在一个内聚单元中。
4)某个事物的信息应局限于一个类中而不要分布在多个类中。通常应由一个单独的类负责保存和操作某特定类型的信息,这个职责不应由多个类分担。如果信息是分布式的,软件将变得更加难以维护,测试也会面临更多挑战。
5)适合时,职责应由相关类共享。很多情况下,各种相关对象必须在同一时间展示同样的行为。例如,考虑一个视频游戏,必须显示如下类:Player、PlayerBody、PlayerArms、PlayerLegs和PlayerHead,每个类都有各自的属性(如position、orientation、color和speed),并且所有这些属性都必须在用户操纵游戏杆时更新和显示。因此,每个对象必须共享职责update()和display()。Player知道在什么时候发生了某些变化并且需要update()操作,它和其他对象协作获得新的位置或方向,但是每个对象控制各自的显示。
3.协作
类有一种或两种方法实现其职责:①类可以使用自身的操作控制各自的属性,从而实现特定的职责;②一个类可以和其他类协作。
协作是以客户职责实现的角度表现从客户到服务器的请求。协作是客户和服务器之间契约的具体实现。如果为了实现某个职责需要发送任何消息给另一个对象,就说这个对象和其他对象有协作。单独的协作是单向流,即表示从客户到服务器的请求。从客户的角度看,每个协作都和服务器的某个特定职责实现相关。
要识别协作可以通过确认类本身是否能够实现自身的每个职责。如果不能实现每个职责,那么需要和其他类交互,因此就要有协作。
为帮助识别协作者,分析师可以检查类之间的3种的通用关系:①is-part-of(是……一部分)关系;②has-knowledge-of(有……的知识)关系;③depends-upon(依赖)关系。在下面的段落中将简单地分别说明这3种通用关系。
属于某个聚合类一部分的所有类可通过is-part-of关系和聚合类连接。考虑前面提到的视频游戏中所定义的类,PlayerBody是Player的一部分,PlayerArms、PlayerLegs和PlayerHead也类似。在UML中,使用如图3-14所示的聚合方式表示这些关系。
图3-14 复合聚合类
当一个类必须从另一个类中获取信息时,就建立了has-knowledge-of关系。
所有情况下,把协作类的名称记录在CRC模型索引卡上,紧靠在协作的职责旁边。因此,索引卡包含一个职责列表,以及相关的能够实现这些职责的协作(参考图3-13)。
当开发出一个完整的CRC模型时,干系人可以使用如下方法评审模型。
①所有参加(CRC模型)评审的人员拿到一部分CRC模型索引卡。拆分协作卡片(也就是使每个评审员不得有两张存在协作关系的卡片)。
②分类管理所有的用例场景(以及相关的用例图)。
③评审组长细致地阅读用例。当评审组长看到一个已命名的对象时,给拥有相应类索引卡的人员一个令牌。
3.6.5 关联和依赖
在很多例子中,两个分析类以某种方式相互联系着,就如同彼此相互联系的两个数据对象。在UML中,这些联系被称为关联(associations)。在某些情况下,关联可以更进一步地指出多样性,其中“一个或多个”使用1..表示,“0或多个”使用0..表示。在UML中,星号表示范围无上界。其他的多样性关联(一对一、一对多、多对多、一对某指定上下限的范围等)可以标识为关联的一部分。
在很多事例中,两个分析类之间存在客户-服务器关系。这种情况下,客户类以某种方式依赖于服务器类并且建立了依赖关系。依赖是由一个构造型(stereotype)定义的。在UML中,构造型是一个“可扩展机制”,允许软件工程师定义特殊的建模元素,这些建模元素的语义是由用户自定义的。在UML中,构造型用一对尖括号表示(如<<stereotype>>)。