建立软件系统体系结构模型
为了帮助确保您的软件系统或应用程序满足用户需求,您可以在 Visual Studio 旗舰版中创建模型来作为软件系统或应用程序的总体结构和行为的说明的一部分。 还可以使用模型来描述在整个设计过程中使用的模式。 这些模型可帮助您了解现有的体系结构、讨论更改以及明确传达您的意图。
模型可用于减少自然语言说明中出现的多义性,并帮助您和您的同事直观地显示设计和讨论备选设计。 应将模型与其他文档或讨论一起使用。 模型本身并不表示体系结构的完整规范。
备注
在本主题中,“系统”意指您正在开发的软件。它可以是许多软件和硬件组件的大型集合、单个应用程序或应用程序的一部分。
可以将系统的体系结构分为两个区域:
高级设计。 高级设计描述主要组件,并说明这些组件之间如何进行交互以满足每项需求。 如果系统是大型系统,则每个组件可能具有自己的高级设计,以便显示这些组件是如何由多个更小的组件构成的。
在整个设计过程使用的设计模式和约定。 模式描述实现编程目标的特定方法。 通过在整个设计过程中使用相同的模式,您的团队可以降低进行更改和开发新软件的成本。
高级设计
高级设计描述系统的主要组件,并说明这些组件之间如何进行交互以实现设计目标。 以下列表中的活动与开发高级设计有关,但这些活动不一定按照特定的顺序排列。
如果您要更新现有代码,则可以先从描述主要组件开始。 请先确保您了解对用户需求的任何更改,然后再添加或修改组件之间的交互。 如果您要开发新系统,则可以从了解用户需求的主要功能开始。 然后,您可以探究主要用例的交互序列,再将这些序列合并为一个组件设计。
对于上述每种情况,并行开发不同的活动并在早期阶段开发代码和测试会很有用。 避免尝试在开始这些方面中的某个方面前完成另一个方面。 通常,在您编写和测试代码时,相关需求和您对系统的最佳设计方法的理解会发生更改。 因此,您应先了解需求和设计的主要功能并对其进行编码。 在稍后的项目迭代中填充详细信息。
了解需求。 任何设计的第一步都是要清楚地了解用户需求。
体系结构模式。 您对系统的核心技术和体系结构元素做出的相关选择。
组件及其接口。 您可以绘制组件图来显示系统的主要部件以及它们进行交互所使用的接口。 每个组件的接口均包含您在序列图中标识的所有消息。
组件之间的交互。 您可以为每个用例、事件或传入消息绘制序列图来显示系统的主要组件之间如何交互以实现所需响应。
组件和接口的数据模型。 您可以绘制类图来描述在组件之间传递并存储在组件内部的信息。
了解需求
开发完整应用程序的高级设计的最有效方式是,将此高级设计与需求模型或用户需求的其他说明一起开发。 有关需求模型的更多信息,请参见用户需求建模。
如果您正在开发的系统是大型系统中的一个组件,则可以将您的部分或所有需求体现在编程接口中。
需求模型提供以下基本信息:
提供的接口。 提供的接口会列出系统或组件必须向其用户提供的服务或操作,无论其用户是实际用户还是其他软件组件。
必需的接口。 必需的接口会列出系统或组件可以使用的服务或操作。 在某些情况下,您可以将所有这些服务设计为您自己的系统的一部分。 在其他情况下,尤其是在您设计可以在多个配置中与其他组件组合的组件时,将基于外部考虑因素来设置必需的接口。
服务质量需求。 系统必须实现的性能、安全性、可靠性以及其他目标和约束。
需求模型是从系统用户的角度进行编写的,无论系统用户是实际用户还是其他软件组件。 系统用户不了解系统的内部工作原理。 相比之下,体系结构模型旨在说明内部工作原理,并演示其如何满足用户需求。
将需求模型和体系结构模型保持分离很有用,因为这样做更易于与用户讨论需求。 此外,还可以帮助您在保持需求不变的同时,重构设计并考虑备选体系结构。
可通过以下两种可选方法来分离需求模型和体系结构模型:
将这两类模型置于相同解决方案的不同项目中。 二者将在 UML 模型资源管理器中显示为单独的模型。 不同的团队成员可以并行处理这些模型。 可以在模型之间创建有限类型的跟踪。
将这两类模型置于相同 UML 模型的不同包中。 这样一来,便能够更轻松地跟踪模型之间的依赖项,但会阻止多个用户同时处理该模型。 此外,将非常大的模型加载到 Visual Studio 中需花费较长的时间。 因此,这种方法不太适用于大型项目。
应放入需求模型或体系结构模型中的详细信息量取决于项目的规模以及团队的大小和分布。 在短项目的小型团队比速写业务概念和数组的选件类图可能只没有进一步设计模式;大型项目分配在多个区域需要更详细信息。
体系结构模式
在开发过程的早期,您必须选择设计所依赖的主要技术和元素。 必须做出以下几个方面的选择:
基础技术选择,例如,数据库和文件系统之间的选择、联网应用程序和 Web 客户端之间的选择等。
框架选择,例如 Windows Workflow Foundation 和 ADO.NET Entity Framework 之间的选择。
集成方法选择,例如企业服务总线和点对点通道之间的选择。
这些选择经常由服务质量需求(如范围和灵活性)确定,可以在获知详细需求之前做出这些选择。 在大型系统中,硬件和软件的配置是密切相关的。
您所做的选择会影响您使用和解释体系结构模型的方式。 例如,在使用某个数据库的系统中,类图中的关联可能表示该数据库中的关系或外键;而在基于 XML 文件的系统中,关联可能指示使用 XPath 的交叉引用。 在分布式系统中,序列图中的消息可以表示传输中的消息;在独立的应用程序中,这些消息可以表示函数调用。
组件及其接口
本节中的主要建议如下所示:
创建组件图以显示系统的主要部件。
绘制组件或其接口之间的依赖项以显示系统的结构。
使用组件上的接口来显示每个组件提供或需要的服务。
在大型设计中,可以绘制独立的关系图以将每个组件分解成多个更小的部件。
本节的其余部分将详细阐述以上几点。
组件
体系结构模型的中心视图是一些组件图,它们显示系统的主要部件及其相互依赖关系。 有关组件图的更多信息,请参见 UML 组件图:参考。
大型系统的典型组件图可能包含类似于以下组件的组件:
演示。 可提供对用户的访问的组件,该组件通常在 Web 浏览器上运行。
Web 服务组件。 提供客户端和服务器之间的连接。
用例控制器。 引导用户完成每个方案的各个步骤。
业务核心。 包含基于需求模型中的类的类、实现主要操作以及施加业务约束。
数据库。 存储业务对象。
日志记录和错误处理组件。
组件之间的依赖项
除组件本身以外,您还可以显示组件之间的依赖项。 两个组件之间的依赖项箭头显示一个组件的设计发生更改会影响另一个组件的设计。 由于一个组件使用的服务或函数是由另一个组件直接或间接提供的,因此该情况会经常出现。
结构良好的体系结构会对依赖项进行清楚地排列,其中满足以下条件:
依赖项关系图中不存在任何循环。
可以将组件排列在层中,其中每个依赖项都将从一个层中的一个组件通向下一个层中的一个组件。 任意两个层之间的所有依赖项都通向同一个方向。
可以显示组件之间的直接依赖项,也可以显示附加到组件的必需接口和所提供接口之间的依赖项。 通过使用接口,可以定义每个依赖项中使用的操作。 通常,在首次绘制关系图时将显示组件之间的依赖项,随着添加了更多的信息,这些依赖项将会替换为接口之间的依赖项。 这两个版本都是对软件的正确描述,但带接口的版本会提供比前一个版本更多的详细信息。
管理依赖项对生产可维护的软件而言非常重要。 组件图应反映代码中的所有依赖项。 如果代码已存在,请确保关系图中显示了所有依赖项。 如果代码正处于开发中,请确保代码不包含组件图中未计划的依赖项。 可以生成层关系图来帮助您发现代码中的依赖项。 可以对照层关系图来验证代码,以帮助您确保符合计划的依赖项约束。 有关更多信息,请参见层关系图:参考。
接口
通过在组件上放置接口,可以分隔并命名由每个组件提供的主要操作组。 例如,基于 Web 的销售系统中的组件可以具有三个接口:一个供客户用来购买商品的接口、一个供供应商用来更新其目录的接口和一个用来管理系统的接口。
一个组件可以具有任意数量的提供的接口和必需的接口。 提供的接口显示该组件提供给其他组件使用的服务。 必需的接口显示该组件在其他组件中使用的服务。
如果同时定义提供的接口和必需的接口,则可以帮助您将该组件与设计的其余部分明确分离,以便您能够使用以下技术:
将该组件置于一个模拟周围组件的测试工具中。
开发独立于其他组件的组件。
通过将该组件的接口耦合到不同的组件,在其他上下文中重用该组件。
若要定义某个接口中的操作列表,可以在 UML 类图上创建该接口的另一个视图。 为此,请在 UML 模型资源管理器中找到该接口,并将其拖动到类图上。 然后,可以向该接口添加操作。
UML 接口中的操作可以表示用于调用组件行为的任何方式。 它可能表示 Web 服务请求、其他类型的信号或交互,或者普通程序函数调用。
若要确定要添加的操作,请创建序列图以显示组件之间的交互方式。 请参见组件之间的交互。 其中的每个序列图显示的是每个不同的用例中出现的交互。 通过这种方式,您可以在浏览用例时,将操作逐渐添加到每个组件的接口中的操作列表。
将一个组件分解为多个部件
可以对每个组件应用前面各节中所述的过程。
在每个组件的内部,可以将其子组件显示为部件。 实际上,部件是其父组件(类的一种)的一个特性。 每个部件都有其自己的类型(可以是一个组件)。 可以将此组件置于关系图上并显示其部件。 有关更多信息,请参见UML 组件图:准则。
将此技术应用于整个系统会很有用。 将系统绘制成一个组件,并将其主要组件显示为部件。 这将有助于您明确标识用于与外界环境连通的系统接口。
当一个组件设计为使用另一个组件时,您通常可以选择是将该组件表示为一个部件,还是将该组件表示为通过必需的接口访问的单独组件。
在以下情况下,请使用部件:
父组件的设计必须始终使用部件的组件类型。 因此,部件的设计是父组件设计的组成部分。
父组件本身实际并不存在。 例如,您可以拥有一个名为“表示层”的概念组件,它表示用于处理视图和用户交互的实际组件的集合。
在以下情况下,请使用通过必需的接口访问的单独组件:
在运行时,可以通过必需组件的接口将其耦合到各个提供组件。
在此设计中,可以轻松地将一个提供程序替换为另一个提供程序。
通常,使用必需的接口要优先于使用部件。 虽然此设计花费的时间更长,但生成的系统会更灵活。 此外,单独测试组件会变得更加轻松。 这可减少开发计划中的耦合。
组件之间的交互
本节中的主要建议如下所示:
标识系统的用例。
为每个用例绘制一个或多个关系图,以显示系统组件如何通过相互协作以及与用户的协作来获得所需结果。 通常,这些关系图为序列图或活动图。
使用接口来指定每个组件收到的消息。
描述接口中的操作的作用。
对每个组件重复该过程,说明其部件的交互方式。
例如,在基于 Web 的销售系统中,需求模型可能会将客户采购定义为用例。 可以创建序列图以显示客户与表示层中的组件进行的交互,并显示客户与仓库和记帐组件进行的交互。
标识启动事件
对于大多数软件系统而言,可以按照其对不同的输入或事件的响应方便地对其完成的工作进行划分。 启动事件可能是下列事件之一:
用例中的第一个操作。 在需求模型中,它可能显示为用例中的一个步骤,或活动图中的一个操作。 有关更多信息,请参见UML 用例图:准则和UML 活动图:准则。
编程接口上的消息。 如果正在开发的系统是大型系统中的一个组件,则应将此系统描述为该组件的某个接口中的一个操作。 请参见组件及其接口。
由系统监视的某个特定条件,或常规事件(如一天中的某个时间)。
描述计算
绘制序列图以显示组件响应初始事件的方式。
为参与典型序列的每个组件实例绘制生命线。 在某些情况下,每个类型可能有多个实例。 如果已将整个系统描述为一个组件,则该组件包含的每个部件都应有一个生命线。
有关更多信息,请参见UML 序列图:准则。
在某些情况下,活动图也很有用。 例如,如果您的组件具有连续的数据流,则可以将该组件描述为对象流。 如果您的组件具有复杂的算法,则可以将该组件描述为控制流。 确保清楚地说明执行每项操作的组件,例如通过注释来加以说明。 有关更多信息,请参见UML 活动图:准则。
指定操作
关系图显示由每个组件执行的操作,其表示形式为序列图上的消息或活动图中的操作。
将每个组件的这些操作收集在一起。 在组件上创建提供的接口,并向这些接口添加操作。 通常,单独的接口用于每个类型的客户端。 在**“UML 模型资源管理器”**中可以非常轻松地向接口添加操作。 按照相同的方式,从其他组件收集每个组件使用的操作,并将这些操作置于附加到该组件的必需的接口中。
向活动图或序列图中添加注释以说明每个操作完成后所获得的结果,这样做会很有用。 此外,也可以在每项操作的**“本地后置条件”**属性中编写其作用。
组件和接口的数据模型
定义参数并返回组件接口中的每个操作的值。 如果操作表示调用(如 Web 服务请求),则参数是作为请求的一部分发送的小段信息。 如果从操作返回多个值,则可以使用**“方向”属性设置为“输出”**的参数。
每个参数和返回值都具有一个类型。 可以使用 UML 类图来定义这些类型。 您无需在这些关系图中表示实现详细信息。 例如,如果您描述的是作为 XML 传送的数据,则可以使用关联来表示 XML 节点之间的任何类型的交叉引用,并使用类来表示节点。
使用注释来描述对关联和特性的业务约束。 例如,如果客户订单上的所有项都必须来自同一个供应商,则可以通过引用订单项与产品目录上的项之间的关联,以及目录项与其供应商之间的关联来描述此约束。
设计模式
设计模式概述如何设计软件的某个特定方面,尤其是在系统的不同部分重复出现的某个方面。 通过在整个项目中采用统一的方法,可以降低设计成本、确保用户界面的一致性以及降低了解和更改代码的成本。
某些常规设计模式(如 Observer)不仅众所周知而且应用广泛。 此外,有些模式仅适用于您的项目。 例如,在 Web 销售系统中,代码中将包含几项用于更改客户订单的操作。 若要确保每个阶段准确地显示订单状态,所有这些操作都必须遵循用于更新数据库的特定协议。
软件体系结构的一部分工作是确定设计过程中应采用的模式。 由于随着项目的进行,将不断发现新的模式和对现有模式的改进,因此这通常是一项持续的任务。 精心组织开发计划以便在早期阶段实施每个主要设计模式,这样做将很有帮助。
可以将大多数设计模式部分包含到框架代码中。 可以将模式的一部分简化为要求开发人员使用特定的类或组件,例如,用于确保正确处理数据库的数据库访问层。
设计模式是在文档中描述的,它通常包含以下几个部分:
名称。
设计模式所适用的上下文的说明。 开发人员应在什么条件下考虑应用此模式?
设计模式所解决的问题的简要说明。
主要部件及其关系的模型。 这可能是类或组件和接口,以及它们之间的关联和依赖项。 元素通常分为两类:
开发人员必须在使用模式的代码的每个部分中复制的元素。 可以使用模板类型来描述这些元素。 有关更多信息,请参见UML 用例图:参考。
描述开发人员应使用的框架类的元素。
使用序列图或活动图的部件之间的交互模型。
命名约定。
有关模式解决问题的方式的说明。
有关开发人员可能能够采用的变体的说明。