活动: 类的设计
类是设计工作的核心,系统的实际工作其实也是由类执行的。子系统、包、封装体以及协作关系等其他设计元素只是说明了类的组合方式或协同操作方式。 在封装体表示并行执行线程的设计之中,设计类是“被动”类,它们存在于由封装体提供的执行环境内。在封装体不起作用的设计中,有些设计类将是“主动”类,这些类协调和驱动被动类的行为。 创建初始设计类首先为指定为活动输入的分析类创建一个或多个(初始)设计类,然后指定追踪依赖关系。在后续步骤中指定各种描述分析类的设计方式的“设计”特征(如操作、方法和状态机)时,将对在此步骤中创建的设计类加以改进、调整、拆分和/或合并。 根据将要设计的分析类类型(边界类、实体类或控制类),可以采用不同的特定策略来创建初始设计类。 设计边界类用户界面中的每个窗口或每个窗体都使用一个边界类是分析中的一条通则。作为相应的结果,边界类担负了相当高的层次的责任,并需要在此步骤中加以改进和详细说明。 边界类的设计还要依赖项目可用的用户界面(或 GUI)开发工具。使用当前的技术,直接在开发工具中采用可视化方式构建用户界面是比较普遍的作法,并可由此自动创建与控制类和/或实体类的设计有关的必要用户界面类。如果 GUI 开发环境自动创建了实施用户界面必需的支持类,那么就无需再考虑它们,只要设计开发环境没有创建的支持类即可。 可执行用户界面原型的草图或屏幕转储也是此项工作的输入,它们可能已经建好,并用于进一步说明对于边界类的需求。 由于边界类的内部行为通常比较复杂,所以一般用子系统作为表示现有系统接口的边界类的模型。如果接口行为简单(仅作为现有 API 到外部系统的传递),则可以选择一种或多种设计类来表示这一接口。这样选择需要为每一协议、接口或 API 都采用一个单一的设计类,并需要在该类的特殊需求中说明有关使用标准以及有关信息的特殊需求。 设计实体类在分析中,实体类代表受控的信息单元;实体对象通常是被动的和永久性的。这些实体类可能已在分析中确定,并且与永久性分析机制相关联。基于数据库的永久性机制的细节是在活动:数据库设计中设计的。出于性能的考虑,可能要强制重组某些永久类,并将因此变更角色: 数据库设计员和角色:设计员之间共同讨论得出的设计模型。 有关永久类的设计问题的进一步讨论,请参见以下的确定永久类部分。 设计控制类控制对象负责管理用例流并协调用例流的大部分操作;控制对象还封装与用户界面问题(边界对象)或数据工程问题(实体对象)没有特殊关系的逻辑。这一逻辑有时也称为应用逻辑或业务逻辑。 因此,设计控制类时至少需要考虑以下问题:
注意,在后两种情况下,如果控制类代表独立的控制线程,则采用工件: 封装体进行控制线程建模更为恰当。 确定永久类必需在永久媒介上保存状态的类被称为“永久类”。保存其状态可以满足下列需要:永久记录有关类的信息、为应对系统发生故障的情况而进行备份或交换信息。永久类可能同时具有永久性和暂时性实例;标记为“永久性”的类只是表明类的某些实例需要是永久性的。 确定永久类通知了角色: 数据库设计员特别注意类的物理存储特征。此外还通知了角色: 构架设计师,类必需是永久性的;并通知负责永久性机制的角色:设计员,类的实例必需是永久性的。 由于需要协调永久性策略,角色: 数据库设计员负责使用永久性框架将永久类映射到数据库中。如果项目正在开发永久性框架,则框架开发人员还要负责理解设计类的永久性需求。要为上述人员提供必需的信息,只需在此说明类(或更准确一点,是类的实例)是永久性的即可。此外还要包含分析中与永久性机制有关的所有设计机制。 示例 永久性的分析机制可以通过以下任意一种设计机制实现:
请注意永久对象不仅可从实体类中派生;而且永久对象还需要处理一般的非功能性需求。以上示例是保持与流程控制有关的信息或事务之间的状态信息所需的永久对象。 定义类的可见性确定每个类在其所在的包中的可见性。“公有”类可由它所在的包之外的类引用。“私有”类(或类的可见性是“实施”)只能由同一包内的类引用。 确定操作确定操作要确定有关设计类的操作,需要:
由于脚本和消息(临时消息规约,尚未指定给操作)说明了类要执行的既定行为,因此操作应该支持序列图中的消息内容。序列图的示例如下: 消息是确定操作的基础。 用例实现无法提供足以确定所有操作的有关信息。要查找其余操作,可以考虑下列问题:
不要定义只获取和设置公有属性值的操作(请参见确定属性和定义关联关系);这些操作一般是代码生成器产生的,不必作出明确定义。 命名和说明操作在命名操作、返回类型和参数及其类型时,应该使用实施语言的命名约定;工件:设计指南对这些约定作出了说明。 对于每项操作,您应该定义下列内容:
定义操作之后,即可使用为每个消息调用的操作的有关信息来完成序列图。 详情请参考指南:设计类中的“类操作”部分。关于 Visual Basic® 的详细指南,亦可参考指南:Visual Basic 中的设计类,“定义操作”部分。 定义操作可见性对每项操作,需要确定操作的导出可见性。可见性的可选类型如下:
选择限定性最强但仍可完成操作目标的可见性。为此需要查看序列图,对于每条消息,需要确定消息是来自接收方所在包外的类(要求公有的可见性),还是来自所在包内的类(要求实施可见性),或者是来自子类(要求保护的可见性),或者来自类本身或类的友元(要求私有的可见性)。 定义类操作绝大部分操作是“实例”操作,即它们是对类的实例执行的操作。但在有些情况下,如果一个操作适合于类的所有实例,则它就是类范围之内的操作。“类”操作的接收方实际上是元类的实例,是对于类本身的说明,而不是类的任何特定实例。类操作的示例包括创建(实例化)新实例和返回类的 allInstances 等消息。 为了表示类范围之内的操作,操作字符串加有下划线。 定义方法方法制定了操作的实施。在已使用操作名、说明和参数充分定义了操作所需的行为的大多数情况下,方法是直接由编程语言实施的。如果实施操作需要采用特定算法,或需要操作说明之外的更多信息,则要采用单独的方法说明。方法不仅说明了操作的内容,而且还说明了其工作方式。 说明方法应该涉及到以下内容:
需求当然会因情况而异。但类的方法规约务必应该陈述如下内容:
更为明确的需求需要侧重于:
序列图是上述信息的重要来源。从序列图中可以清楚地了解执行操作时,在其他对象中使用的操作。有关要在其他对象中使用哪些操作的规约对于完全实施操作是十分必要的。因此,生成完整的方法规约要求您确定用于所涉及对象的操作并检查相应的序列图。 有关 Visual Basic® 的详细指南,请参考指南:Visual Basic 中的设计类,“定义(UML)方法”部分。 定义状态对于某些操作,操作行为依赖于接收方对象的状态。状态机是一种适用于此的工具,它描述对象可以具有的状态和导致对象状态变化的事件,请参见指南:状态图。 状态机对说明“主动类”非常实用,并且它在定义工件: 封装体时也起到了很大的作用。 简单状态机的示例如下: 燃油分配器的简单状态图 每一状态转移事件都与一个操作关联关系。取决于对象的状态,操作可以具有不同的行为;转移事件对此作出了说明。 对于关联关系操作的方法说明应该和相应状态的信息一起更新,对每个有关状态要说明应该执行的操作。状态通常采用属性表示;状态图可以作为确定属性的步骤的输入。 有关详细信息,请参见指南: 状态图。 关于 Visual Basic® 的详细指南,请参考指南:Visual Basic 中的设计类,“定义状态”部分。 确定属性在方法的定义和状态的确定中,确定了类执行其操作所需的属性。 属性为类实例提供了信息存储空间,并通常用于表示类实例的状态。类本身保留的任何信息都是通过属性实现的。对于每种属性,需要定义以下内容:
检查以确保所有属性都是必需的。应该验证属性 - 在流程早期添加属性比较容易,并且如果属性不是由于缺乏预见性而变得毫无用处,它存活的时间也可以更长。由于成千上万的实例而成倍增加的过多属性将会严重影响系统性能和存储需求。 有关属性的详细信息,请参考指南: 设计类中的“属性”部分。 定义依赖关系对于要求在对象之间进行通信的情况,需要作出以下提问:
定义关联关系关联关系是对象之间相互通信的机制。它们为对象提供了一个消息流动的“通道”。关联关系还记录了类之间的依赖关系,强调了对于某类所作变更将会影响到很多其他的类。 检查每种操作的方法说明,以便理解类实例和其他对象进行通信与协作的方式。为了给另一对象发送消息,对象必须具有对于消息接收方的引用。协作图(序列图的另一表示形式)利用链接描述对象之间的通信,如下图所示: 定义关联关系和聚合关系其余消息可以采用关联关系或聚合关系来确定进行通信的两个类的实例之间的关系。 有关选择适当表示方法的信息,请参见指南:关联关系和指南:聚合关系。对于这两种关联关系,在协作图中将链接的可见性设置为“域”。其他任务包括:
最好在描述关联类的类图中定义关联关系和聚合关系。类图应该由包含关联类的包拥有。描述关联关系和聚合关系的类图示例如下: 此类图示例显示类之间的关联关系、聚合关系和泛化关系。 处理分析类之间的订阅关联关系分析类之间的订阅关联关系用于确定类之间的事件依赖关系。在设计模型中,我们必须明确处理这些事件依赖关系,或者采用现有的可用事件处理器框架,或者通过设计和构造自己的事件处理器框架来进行处理。在某些编程语言(如 Visual Basic)中,只需简单地声明、提出和处理相应事件即可。在其他语言中,您可能需要使用某些可复用函数的附加库来处理订阅关系和事件;如果无法获取相应功能,则必须加以设计和创建。另请参见指南: 订阅关联关系。 定义泛化关系类可以组织成泛化关系分层结构,来反映公共的行为和结构。在此可以定义公共超类,以便子类可以继承该超类的行为和结构。泛化关系是一种用于标记的便利手段,只要使用它在一个地方标定公共的行为和结构,那么如果在其他地方发现重复性的行为和结构,就可以再次使用它们。有关详细信息,请参考指南: 泛化关系。 在发现可以泛化关系的行为和结构时,应创建一个公共超类,来包含公共属性、关联关系、聚合关系与操作。并从将要成为公共超类的子类的类中删除公共的行为。定义从子类到超类的泛化关系。 解决用例冲突
通过设计流程顺序处理用例的困难之一是,两个或多个用例可能使用冲突的方式、同时尝试调用对设计对象进行操作。此时必须确定并行冲突并加以解决。 如果采用同步消息传递的方式,则只有一个操作完成,才可以进行后续的调用对象操作。同步的消息传递表明按照消息的先后顺序处理消息。在所有消息具有相同优先级,或每个消息在同一执行线程中运行的情况下,这一方式可以解决并行冲突。在不同的执行线程(以工件:封装体表示)访问对象的情况下,必须采取显式机制来避免或解决并行冲突。 不同的执行线程可以同时调用对同一对象执行的不同操作,而不会产生并行冲突;同时修改客户名和地址也不会产生冲突。只有在两个不同的执行线程试图修改对象的同一特征时,才会产生冲突。 对每个可能同时被不同执行线程访问的对象而言,需要确定不可同时访问的受保护代码区段。但在精化阶段初期,由于必须保护的操作很多,确定具体的代码段几乎是不可能的。接下来,选择或设计合适的访问控制机制来避免发生同时进行访问的冲突。串行化访问的消息队列、使用每次仅允许访问一个线程的信号(或“令牌”)或其他形式的锁定机制都是此类机制的示例。机制的选择往往在很大程度上要依赖于实施,并且会因编程语言和操作环境而异。有关选择并行机制的指南,请参见项目专用的工件: 设计指南。 处理一般的非功能性需求设计类应该加以改进,以便处理一般的非功能性需求,项目专用的设计指南对此作出了说明。对分析类的非功能性需求是此步骤的一个重要输入,分析类的特殊需求和职责可能已对这些需求作出了说明。这种需求通常都根据实现类需要的构架(分析)机制而定;然后在此步骤中加以改进,合并与这些分析机制有关的设计机制。 可用的设计机制由构架设计师在工件: 设计指南中确定,并说明其特征。对于每种所需的设计机制,尽可能多地限定它们的特征,并给定适用范围。有关设计机制的详细信息,请参考活动: 确定设计机制,概念: 分析机制和概念: 设计机制与实施机制。 设计类时,需要考虑的通用设计指南和机制如下:
有关 Visual Basic® 的详细指南,请参考指南:Visual Basic 中的设计类,“处理一般的非功能性需求”部分。 评估结果在此阶段,您应该检查设计模型来核实工作是否偏离正轨。您没有必要详细复审模型,但应该考虑以下检查点: |
Rational Unified Process |