活动:
|
目的 | 确定系统必须响应的外部和内部的事件和信号。 |
事件是引起系统内的某个操作的外部和内部发生的情况。事件及其特征可有助于驱动关键设计元素(如活动类)的确定。
一系列初始的外部事件可从用例模型和参与者与用例的交互中得出。内部事件可从用例流的文本中得出,或在设计发展时确定。
事件的重要特征是:
事件的特征应按需捕获,以驱动处理事件的设计元素的确定。捕获事件特征在反应(事件驱动)系统中往往是最重要的,但在其它系统中也是有用的,例如使用并发和/或异步消息传递的系统。
异步通信事件可建模成信号,以表达承载的数据或表达信号之间的关系(如泛化关系)。在某些系统中,特别是反应系统中,使从外部设备接收的信号与特定机制(如中断或特定轮询消息)相关是很重要的。
目的 | 将分析类优化成相应的设计模型元素 |
确定类。当分析类很简单并已经表示单个逻辑抽象时,可直接映射(1:1)到设计类。通常,实体类在设计中会相对原封不动地保存下来。由于实体类通常也是持久的,因此请确定设计类是否应是持久的,并相应记录在类描述中。
当确定类时,应将类分组到工件:设计包中,以用于组织和配置管理目的。 有关如何做出封装决策的更多信息,请参阅指南:设计包。
确定活动类。请考虑已确定分析对象的环境中的系统并发需求:需要系统响应外部生成的事件吗?并且如果需要,当事件发生时,哪些分析类是“主动”的?用例模型中的外部事件由来自参与者并与用例交互的激励表示。请看相应的用例实现,以查看事件发生时哪些对象进行交互。从将对象一起分组到自主的各组协作对象开始 - 这些分组表示形成组合活动类的组的初始划分。
如果事件具有需要捕获的重要属性,则考虑将它们建模成类,定型为 <<信号>>。
活动类的实例表示独立的“逻辑”执行线程。这些“逻辑”执行线程不能与操作系统中的执行线程发生混淆,也不会字面映射到操作系统中的执行线程(虽然在某个时刻会将它们映射到操作系统执行线程)。 相反,它们表示解决方法空间中独立的概念上的执行线程。此时在设计中确定它们的目的是为了能够基于系统中的固有“并发缝隙”将解决方法划分成独立单元。 以此方法划分工作使得概念上处理并发的问题更加简单,因为独立的执行线程可单独处理,除非它们达到共享底层被动类的程度。
通常,只要在问题领域内存在并发和并发冲突,就应该考虑活动类。活动类应该用于表示某个外部并发对象或计算机内的并发活动。这使我们能够监视和控制并发活动。
另一个自然的选择是使用活动类作为连接到计算机的外部物理设备的内部表示,因为那些物理实体在本质上是并发的。这些“设备驱动程序”类不仅用于监视和控制相应的物理设备,而且将系统的剩余部分与设备细节隔离开来。这意味着即使设备使用的技术有发展,系统的剩余部分也不会受影响。
使用活动类的另一个常见情况是表示逻辑并发活动。逻辑活动表示概念上并发的“对象”,例如财务交易或电话呼叫。尽管事实上这些并不直接显示为物理实体(虽然发生在真实世界中),但这样看待它们通常是有原因的。例如,可能需要暂时冻结特定的财务交易以避免并发冲突,或由于系统内的故障而需要放弃此次交易。 因为这些概念上的对象需要作为单元来操纵,因此将它们表示为具有各自接口(这些接口提供相应功能)的对象是很方便的。
这种类型的概念对象的特定示例是活动对象控制器。它的目的是持续管理一个或多个其它活动对象。这通常包括将每个对象引入期望的操作状态,在面临各种破坏的情况下(如局部故障)使其维持该状态并使它的操作与其它对象的操作同步。这些活动对象控制器通常是从活动:用例分析期间确定的控制对象发展来的。
由于它们的功能是简单而恰当地解决并发冲突,因此活动类作为共享资源的保护者也是很有用的。在这种情况下,多个并发活动需要的一种或多种资源被封装在一个活动类中。依靠其内置的互斥语义,此类保护者会自动保护这些资源免受并发冲突。
确定子系统。当分析类很复杂以至它包含的行为不可能是单独操作的单个类的职责时,应将该分析类映射到设计子系统。使用设计子系统封装这些协作,方法是使子系统客户端可以完全不管子系统的内部设计,即使它们使用该子系统提供的服务也是如此。
子系统建模成 UML 组件,该组件只具有作为公共元素的接口。这些接口提供一层封装,允许子系统的内部设计保持对其它模型元素不可见。概念子系统用于区别于包,这些包是模型元素的无语义容器。
从一组协作分析类中创建子系统的决策很大程度上基于协作是否可以或将要由单独的设计团队独立开发。如果协作可以与协作类一起完全包含在一个包中,则子系统可以提供比简单的包所提供的更强形式的封装。在一个或多个接口后面完全隔离子系统中的内容和协作,以便子系统的客户端仅依赖于该接口。 然后子系统的设计者完全与外部依赖关系隔离;需要该设计者(或设计团队)指定如何实现该接口,但他们完全可以自由更改内部子系统设计,而不会影响外部依赖关系。在有大量独立团队的大型系统中,这一程度的分离与正式接口所提供的体系结构实施相结合,是选择子系统而不是简单的包的一个有力的理由。有关影响选择使用子系统作为设计元素的各种因素的更多信息,请参阅指南:设计子系统。
目的 | 确定使系统中的缝隙正式化的设计元素。 |
接口定义由某个分类器实现的一组操作。在设计模型中,接口主要用于定义子系统的接口。这不是说接口也不能用于类,而是对于单个类,在该类上定义公共操作(实际上是定义它的“接口”)通常就足够了。接口对于子系统是很重要的,因为它们允许行为声明(接口)与行为实现(子系统内实现接口的特定类)相分离。这种分离为我们提供了增强在系统不同部分上工作的开发团队的独立性的方法,同时保留这些不同部分之间的精确“合同”定义。
为每个子系统确定一组候选接口。使用在前一步中确定的已分组的协作,确定当启动协作时要“激活”的职责。然后,通过确定“客户端”必须提供的信息和协作完成时返回的信息来优化该职责;各组信息成为原型输入和输出参数并返回子系统将实现的操作的值。使用工件:特定于项目的指南中定义的命名约定为此操作定义名称。重复此操作,直到子系统将实现的所有操作都已定义。
接下来,按照操作相关的职责,将操作分组在一起。较小的组比较大的组更好,因为组中的操作越少,就越有可能存在一组内聚的公共职责。同样也要关注重用 - 查看相似性,这会使确定相关的可重用功能更容易。但同时,不应花大量时间尝试查找理想的职责分组;请记住,这只是首次划分的分组,优化将在整个精化阶段以迭代方式继续。
请查看接口之间的相似性。从一组候选接口中,查找相似的名称、职责和操作。若相同的操作存在于几个接口中,则重构这几个接口,将共同的操作抽取到一个新的接口中。请确保也查看了现有接口,可能的话重用这些接口。 目标是维护接口的内聚性,同时除去接口之间的冗余操作。这将使接口更易理解并更易随着时间发展下去。
定义接口依赖关系。每个接口操作的参数和返回值都具有特定类型:它们必须实现特定接口或必须是简单数据类型的实例。在参数是实现特定接口的对象的情况下,定义接口及其所依赖的接口之间的依赖关系。定义接口之间的依赖关系为软件设计人员提供了有用的耦合信息,因为接口依赖关系定义了设计模型中的元素之间的主要依赖关系。
将接口映射到子系统。一旦确定了接口,就创建子系统和它实现的接口之间的实现关联。从子系统到接口的实现表明子系统中存在实现接口操作的一个或多个元素。当以后设计子系统时,将优化这些子系统-接口实现,通过子系统设计人员指定子系统内实现接口操作的特定元素。这些优化的实现只对子系统设计人员可见;从子系统客户端的角度来看,只能看到子系统-接口实现。
定义接口指定的行为。 接口通常为实现接口的元素定义隐式状态机。如果接口上的操作必须按特定顺序调用(如数据库连接必须先打开再使用),应该定义状态机,该状态机阐明公开可视的(或推测的)状态(这些状态是实现接口的任何设计元素必须支持的)。 此状态机将帮助接口用户更好了解接口,并将帮助元素(用于实现该接口)的设计人员提供元素的正确行为。
封装接口。接口由软件设计人员所有;对接口的更改在体系结构上始终是很重要的。要管理对接口的更改,应将接口分组到由软件设计人员所有的一个或多个包中。如果所有接口是由一个子系统实现的,则可将接口与子系统放在同一个包中。如果接口是由多个子系统实现的,则应放在由软件设计人员所有的单独的包中。这样可在独立于各自的子系统的情况下管理和控制接口。
按照 UML 1.5,子系统实际上是只将接口作为公共元素的一种特殊的包。这些接口提供一层封装,允许子系统的内部设计保持对其它模型元素不可见。概念子系统用于区别于“普通”的包,后者是模型元素的无语义容器;子系统表示具有类似于类的(行为上的)属性的包的特定用途。
请参考 UML 1.x 和 UML 2.0 之间的区别以获取更多信息。
Rational Unified Process
|