活动:
|
目的
|
|
角色:设计员 | |
频率:每个迭代进行一次。 | |
步骤 | |
输入工件: | 生成的工件: |
工具向导: | |
更多信息: |
工作流程明细: |
类是设计工作的重要部分 - 它们实际上执行系统的真正工作。其它设计元素(如子系统、程序包和协作)描述类如何分组以及如何互相操作。
活动类是协调并推动被动类行为的设计类 - 活动类是这样的类:其实例是拥有自己的控制线程的活动对象。
使用适合设计的类或功能、符合项目设计指南的设计模式和机制。
合并模式和/或机制是有效地执行该活动中的许多后续步骤(添加新类、操作、属性和关系),但是要符合模式或机制定义的规则。
请注意,模式和机制一般随着设计的发展而合并,不只是作为此活动中的第一步。它们还经常在一组类中得到应用,而不仅仅应用于单个类。
为作为此活动的输入给出的分析类创建一个或多个初始设计类并分配跟踪依赖关系。本步骤中创建的设计类将在后续步骤中得到优化、调整、拆分或合并;在后续的步骤中将指定各种描述分析类如何设计的设计属性 - 如操作、方法和状态机。
根据设计的分析类的类型(边界、实体或控制),您可以使用特定的策略创建初始设计类。
边界类代表到用户或其它系统的接口。
一般情况下,代表到其它系统的接口的边界类被建模为子系统,因为它们往往有复杂的内部行为。如果接口行为很简单(也许只是充当到外部系统的现有 API 的传递),那么您可能会选择用一个或多个设计类代表接口。如果您选择这种方式,请对每个协议、接口或 API 使用单个的设计类,并记录关于您在类的特别需求中使用的标准的特别需求。
代表到用户的接口的边界类一般遵循用户界面中的每个窗口对应一个边界类或每种形式对应一个边界类的规则。结果,边界类的职责就可能处于一个相当高的级别,并需要在本步骤中进行优化和详细说明。用户界面的其它模型或原型可能是本步骤中要考虑的另一个输入来源。
边界类的设计取决于项目可用的用户界面(UI)开发工具。使用当前的技术,在开发工具中直接直观地构造 UI 是很常见的。这将自动创建需要与控制和实体类的设计相关的 UI 类。如果 UI 开发环境自动创建实施 UI 所需的支持类,那么就没有必要在设计中考虑这些支持类了。您只设计开发环境没有为您创建的内容。
在分析期间,实体类代表被操纵的信息单元。它们往往是被动的、持久的,并且可能被确定并与持久性分析机制相关联。 关于设计基于数据库的持久性机制的详细信息包含在活动:数据库设计中。性能考虑事项可能会迫使持久类的一定重构,从而造成在角色:数据库设计人员和角色:设计人员之间联合讨论的“设计模型”的更改。
有关持久类的设计问题的更广泛的讨论展示在后面题为确定持久类的部分中。
控制对象负责管理用例的流动并因此协调其大多数操作;控制对象包含的逻辑不是特别与用户界面问题(边界对象)或数据工程问题(实体对象)相关。这种逻辑有时称为应用逻辑或业务逻辑。
在设计控制类时请考虑下列问题:
- 用例协调行为变成嵌入到 UI 中,使更改系统变得更加困难
- 相同的 UI 不能简单地在不同的用例实现中使用
- UI 因为增加了功能而增加负担,结果降低了性能
- 实体对象可能因为用例特定的行为而增加负担,结果降低了其泛化程度
为了避免这些问题的发生,就引进了控制类来提供与协调事件流相关的行为。
在后两种情况下,如果控制类代表独立的控制线程,那么使用活动类来对控制线程建模可能会更加适合。
需要在永久介质上存储其状态的类被称为持久类。存储状态的需要可能是为了永久记录类信息、作为出现系统故障时的备份或用于信息的交换。持久类可能既有持久实例,也有临时实例;将类标注为持久只意味着类的某些实例可能需要持久。
根据在分析期间找到的持久性机制来合并设计机制。例如,根据类的要求,持久性的分析机制可能通过以下设计机制中的一个来实现:
持久对象可能不仅仅从实体类获得;持久对象还可能需要用来处理一般的非功能需求。示例有:维护与流程控制相关的信息或者维护事务之间的状态信息需要持久对象。
确定持久类用来通知角色:数据库设计人员:类要求对其物理存储特征有特别关注。它还通知角色:软件设计人员类需要是持久类,并通知负责持久性机制的角色:设计人员类的实例需要变为持久实例。
由于需要协调的持久性策略,角色:数据库设计人员就负责用持久性框架将持久类映射到数据库。如果项目开发的是持久性框架,则框架开发人员还将负责了解设计类的持久性需求。要向这些人员提供他们所需的信息,此时表明类是持久的,或者(更精确地说)表明类的实例是持久的就足够了。
对于每个类,请确定它所在程序包内的类可视性。公用类可以在包含程序包的外部引用。私有类(或可视性为实施的类)可能只能由同一个程序包内的类引用。
要在设计类上确定操作:
要求操作能支持出现在序列图上的消息,因为脚本 - 尚未分配给操作的临时消息规范 - 描述期望类能执行的行为。图 1 是序列图的一个示例。
图 1:消息成为确定操作的基础
用例实现不能提供足够的信息来确定所有操作。要查找剩下的操作,请考虑下列问题:
不要定义仅仅获得并设置公用属性的值的操作(请参阅定义属性和定义关联)。通常来讲,这些是由代码生成工具生产的,不要求进行显式的定义。
命名操作、返回类型以及参数和它们的类型时,请使用实施语言的命名约定。
对于每个操作,您都应该定义以下内容:
一旦定义了操作,就请用关于每条消息调用了什么操作的信息来完成序列图。
有关更多信息,请参阅“指南:设计类”中题为类操作的部分。
对于每个操作,请从下列选项确定操作的导出可视性:
请选择可能最受限制、但仍能实现操作目标的可视性。要做到这一点,请查看序列图,并为每条消息确定消息是来自于接收者程序包的外部类(要求公用可视性)、来自于程序包的内部(要求实施可视性)、来自于子类(要求受保护可视性)还是来自于类本身或朋友(要求私有可视性)。
大部分操作是实例操作;也就是说,它们是在类的实例上执行的。但是,在某些情况下,操作适用于类的所有实例,因此是类范围的操作。类操作接收者实际上是元类的实例 - 类本身的描述 - 而不是类的任何特定实例。类操作的示例包括创建(实例化)新实例的消息,它们返回类的 allInstances,依此类推。
操作字符串加下划线表示,以表明是类范围的操作。
方法指定操作的实施。如果操作要求的行为由操作名称、描述和参数进行了足够的定义,在许多这样的情况下,方法都是直接在编程语言中实施的。在操作的实施要求使用具体的算法或者要求比操作的描述中更多的信息,就要求有独立的方法描述。方法描述操作如何工作,而不仅仅描述它做什么。
如果方法进行了描述,它就应该讨论:
需求在各个案例中都不尽相同,但是类的方法规范应该总是说明:
更具体的需求可能会涉及:
序列图是这个的重要来源。从这些方面已经能很清楚地了解在执行操作时将在其它对象中使用什么操作。将在其它对象中使用什么操作的规范对于操作的完全实施是必需的。因此,生成完整的方法规范要求您确定所涉及的对象的操作并检查相应的序列图。
对于一些操作,操作的行为取决于接收者对象所处的状态。状态机是一个工具,它描述对象可以呈现的状态以及使对象从一种状态移动到另一个状态的事件(请参阅指南:状态表图)。状态机对于描述活动类最有用。
图 2 中显示了简单状态机的示例。
图 2:加油机的简单状态表图
每个状态转换事件都可能与操作关联。根据对象的状态,操作可能有不同的行为,转换事件描述这种情况如何发生。
关联操作的方法描述应该用特定于状态的信息进行更新,表明操作对每个相关状态应该做什么。状态通常用属性表示;状态表图充当属性确定步骤的输入。
有关更多信息,请参阅指南:状态表图。
在定义方法和确定状态期间,确定类执行其操作所需的属性。属性为类实例提供信息存储,并经常用于代表类实例的状态。类本身保持的任何信息都是通过其属性完成的。对于每个属性,请定义:
检查以确保所有的属性都是需要的。属性应该有存在理由 - 属性可以很容易在流程早期添加,并能在它们不再需要后很长时间内都存在(由于眼光短浅而造成的)。额外的属性由于数千个甚至数百万个实例而变得数量繁多,它们对系统的性能和存储需求可能有不利的影响。
有关属性的更多信息,请参阅指南:设计类中题为属性的部分。
对于每一个要求对象间通信的案例,请提出以下问题:
请注意,用这种方式建模的链接是临时链接,只在协作的特定环境中存在有限的期限 - 从这个意义上来讲,它们是协作中关联角色的实例。但是,如上文所述,类模型中的关系(即,独立于环境)应该是依赖关系。如 [RUM98] 所述,在临时链接的定义中:“将所有这样的链接作为关联进行建模是可能的,但是这样的话,关联的条件就必须陈述得非常宽泛,而且它们在限制对象的组合中失去了许多精确性。”在这种情况下,协作中的关系的建模比依赖关系的建模更重要,因为依赖关系并不完整地描述关系;而仅仅描述它存在而已。
关联向对象提供互相交流的机制。它们向对象提供消息可以流动的导管。它们还记录类之间的依赖关系,突出强调一个类中的更改可以被许多其它的类感觉到。
检验每个操作的方法描述,了解类的实例如何与其它对象互相交流和协作。要将消息发送给另一个对象,对象就必须有到消息的接收者的引用。通信图(序列图的另一种表示方法)将从链接的角度显示对象通信,如图 3 所示。
图 3:通信图的示例
剩下的消息用关联或聚集来指定互相交流的两个类的实例之间的关系。有关选择适当的表示方法的信息,请参阅指南:关联和指南:聚集。请在通信图中将这两种关联的链接可视性都设置为字段。其它任务包括:
关联和聚集在描述关联类的类图中有最好的定义。类图应该由包含关联类的程序包拥有。图 4 是一个描述关联和聚集的类图示例。
图 4:显示类之间的关联、聚集和泛化关系的类图示例
分析类之间的预订关联用于确定类之间的事件依赖关系。在“设计模型”中,您必须明确地处理这些事件依赖关系,通过使用可用的事件处理程序框架或者通过设计并构造您自己的事件处理程序框架。在一些编程语言中 - 如 Visual Basic - 这是直接了当的;您声明、提出并处理相应的事件。在其它语言中,您可能必须使用一些其它的可重用功能库来处理预订和事件。如果功能无法购买,它就需要进行设计和构造。另请参阅指南:预订关联。
一些类可能代表复杂的抽象并拥有复杂的结构。在对类进行建模时,设计人员可能希望代表其内部参与元素以及它们的关系,以确保实施人员将相应地实施在该类内部发生的协作。
在 UML 2.0 中,类被定义为结构化的类,能够拥有内部结构和端口。然后,类可能被分解成互相连接的部分的集合,而这些部分又可以进一步进行分解。可以通过迫使来自外部的通信穿过符合所声明接口的端口来包括类。
当您找到有复杂结构的复杂类时,请为该类创建复合的结构图。对将为该类行为执行角色的部分进行建模。确定如何使用连接器将各部分“连线”。如果要使该类的不同客户端可以访问该类提供的特定部分的行为,请利用带所声明接口的端口。同时请利用端口将该类的内部部分完整地从其环境中分离出来。
有关本主题的更多信息以及复合结构图的示例,请参阅概念:结构化的类。
类可以组织为泛化关系层次结构,以反映通用的行为和通用的结构。可以定义通用的超类,子类既可以从中继承行为,也可以从中继承结构。泛化关系是为了表示上的方便,使您能够在一个地方定义通用的结构和行为,并在找到重复行为和结构的地方重复使用这种通用的结构和行为。有关泛化关系的更多信息,请参阅指南:泛化关系。
当您找到泛化关系时,请创建一个通用超类来包含通用属性、关联、聚集和操作。将通用行为从将成为通用超类的子类的类中除去。定义从子类到超类的泛化关系。
本步骤的目的是防止两个或更多个用例可能同时、用可能不一致的方式访问设计类的实例时造成的并行冲突。
在设计流程中逐个用例地前进中存在的一个困难是:两个或更多个用例可能会尝试用可能冲突的方式同时对设计对象调用操作。在这些情况下,并行冲突必须明确地进行确定和解决。
如果使用了同步消息传递,那么执行操作将阻止对对象的后续调用,直到操作完成为止。同步消息传递意味着消息处理的“先到先服务”的顺序。这可能会解决并行冲突,特别是当所有消息的优先级都相同或者当每条消息都在相同的执行线程内运行的时候。如果对象可能由不同的执行线程(用活动类表示)访问,就必须使用显式机制来防止或解决并行冲突。
同一个对象上的不同操作被不同的执行线程同时调用而不出现并行冲突是可能的;客户的姓名和地址可以并行修改而不起冲突。只有当两个不同的执行线程尝试修改对象的同一个属性时,才会出现冲突。
请为每一个可能会由不同的执行线程并行访问的对象确定必须受保护以免于同时访问的代码段。在“精化”阶段早期,确定具体的代码段是不可能的;必须受保护的操作就足够了。接着,选择或设计适当的访问控制机制来防止互相冲突的同时访问。这些机制的示例包括消息排队以将访问序列化、使用信号或标记允许一次只能访问一个线程或者其它变化形式的锁定机制。机制的选择倾向于在很大程度上取决于实施,并且一般会随着编程语言和操作环境的变化而变化。
“设计类”被优化来处理一般的、非功能的需求中所述。本步骤的重要输入包括分析类的非功能需求,这些需求可能在分析类的特别需求和职责中已经有了说明。这样的需求往往从需要什么样的体系结构(分析)机制来实现类这一方面进行指定;在本步骤中,类然后被优化来合并与这些分析机制相对应的设计机制。
可用的设计机制由软件设计人员中确定和描绘。对于每个需要的设计机制,请使尽可能多的特征合格,在适当的地方给定范围。有关设计机制的更多信息,请参阅活动:确定设计机制、概念:分析机制和概念:设计和实施机制。
在设计类时需要考虑数个一般的设计指南和机制,例如如何:
在此阶段检查设计模型来验证您的工作正朝着正确的方向前进。没有必要详细复审模型,但是您应该考虑以下检查点:
Rational Unified Process
|