软件设计
软件设计是指,针对需求工程给出的软件需求,综合考虑各种制约因素,探求切实可行的软件解决方案并最终给出方案的逻辑表示。这种表示称为设计模型。软件设计的制约因素来自资源和技术两个方面。资源制约因素是指,在目标软件开发过程中可以获取的时间、人力、财力、开发辅助工具等。技术制约因素主要是指待开发目标软件系统可以使用的技术平台。根据长期积累的软件设计经验,设计模型主要应包括以下成份:
(1) 体系结构模型:体系结构(architecture),也称“架构”,它从高层抽象的角度刻画组成目标软件系统的各子系统或构件,以及它们之间的逻辑关联。在一个设计模型中可以存在一系列抽象级别不同的体系结构模型。抽象级别愈低,模型中子系统或构件的粒度越小,软件实现的细节越多。这好比建筑设计,建筑师开始仅考虑楼宇的位置、朝向和外观,继而设计楼层内各房间的布局,最后才会细化至房间内部的装饰设计。
(2) 用户界面模型:供用户使用目标软件系统内含的各项功能的图形用户界面,包括界面元素的组织、布局、预期响应行为、呈现形态(显示或隐藏,使能或变灰等)、界面流转(一个界面在响应用户的界面动作后切换至另一界面)等。
(3) 用例设计模型:描述设计元素如何协同完成用例所要求的功能的设计模型。
(4) 子系统及构件的设计模型:体系结构模型中的每个子系统或构件都有一个设计模型,以描述其对外接口,组成子系统或构件的内部元素(例如类、函数)的属性、算法,以及这些内部元素之间的协作关系。
(5) 数据模型:希望持久保存的数据条目的内部结构及条目之间的逻辑关系。持久保存是指,目标软件结束运行后,其生成的部分数据仍能保存于数据库或文件系统中,以便为其他应用软件所访问,或供本软件下次运行时使用。
为什么不直接将软件需求模型映射成实现代码?为什么必须在需求工程与软件构造两个阶段之间引入软件设计?这是因为,软件实现代码中包含了太多细节,无法对其进行有效的评估以确定其是否满足用户需求、是否具备良好的软件质量属性,一旦发现缺陷后再来修改程序代码的代价太高。软件设计模型比程序代码更易评估、更易修改。软件设计的必要性还表现在,针对大型软件的需求寻求软件解的过程必然是一个循序渐进、不断精化的迭代过程,必须自顶向下、先结构后细节,必须借鉴前人的软件设计经验,必须综合比较多种设计方案择优采用。只有设计模型,而非程序代码,才能有效地支持这一过程。因此,软件设计是需求模型与实现代码之间的“桥梁”,是达成软件质量目标的关键性阶段。
软件设计的目标是,获取能够满足软件需求的、明确的、可行的、高质量的软件解决方案。“明确”是指,软件设计模型易于理解,软件构造者在设计方案的实现过程中,勿需再面对影响软件功能和质量的技术抉择或权衡。“可行”是指,在可用的技术平台和软件项目的可用资源条件下,采用预定的程序设计语言可以完整地实现该设计模型。“高质量”是指,设计模型不仅要给出功能需求的实现方案,而且要使该方案适应非功能需求的约束;设计模型要尽量优化,以确保依照设计模型构造出来的目标软件产品(在排除软件构造阶段引入的影响因素后)能够表现出良好的软件质量属性,尤其是正确性、有效性、可靠性和可修改性。
为达成上述目标,软件设计师可能面临以下挑战:
(1) 求解。需求模型主要描述目标软件产品要“做什么”,设计模型则要回答“如何做”。填平两者之间的鸿沟无疑是一项兼具创造性和工程性的工作,需要软件设计师对需求模型和软件实现技术的透彻理解和灵活把握,需要综合运用设计师本人和其他软件设计者长期积累的相关设计经验,这些经验由于经受了实践检验而弥足珍贵。
(2) 抉择。对于同一项软件需求,可行的技术实现方案往往有多个,设计师必须明辨各种方案的优缺点,综合考虑各种因素,并对多种方案的组合效应了然于胸。
(3) 抽象。在迭代式设计求精的过程中,过早地沉浸于细节决非正确的设计之道。在特定的设计子阶段或步骤,必须区分结构性、全局性、关键性问题与过程性、局部性、枝节性问题。抽象是分层的,软件设计一般要自顶向下地经历一系列抽象级别不同的设计阶段,后续阶段会在前一阶段的基础上引进更接近于软件实现的设计元素,这一过程称为“逐步求精”。在此意义上,抽象是相对的,在前驱阶段上的局部性问题可能演化为后续阶段中的全局性问题。因此,在进入给定设计阶段之初,如何决定当前待解决的设计问题的取舍,适当延迟细节性问题至后续阶段,殊非易事。
(4) 灵巧。软件对需求变化的适应能力主要来源于软件的结构设计和柔韧性。这就要求软件设计师预判未来可能的需求变化,并为此设计软件模块的扩充机制或更新机制。更严峻的挑战来源于以下事实:许多需求变化并非事先可以预测;同时,过多地预设扩充机制有时会损害目标软件产品的简洁性,降低其运行效率。如何在柔韧性与简单性、高效性之间取得合理的平衡,如何应对难以预知的需求变化,是长期以来困扰软件设计师的主要技术难题。
1 软件设计的过程模型
1.1 设计活动
一般而言,软件设计过程包含以下活动:
(1) 策划。如前所述,软件设计是一个自顶向下、迭代求精的过程。在启动每次迭代之前,有必要明确本次迭代的目标及认可准则,确定本次迭代的抽象级别(即,关注哪些设计问题,忽略哪些细节)、输入/输出制品、资源需求,制订工作计划。本次迭代中的所有设计活动将遵照此计划来执行。
(2) 用户界面设计。在需求工程阶段得到的用户界面情景板、界面原型的基础上,针对用例所描述的用户与软件系统之间的交互动作,设计为实现用例所必需的每一个屏幕(窗口、对话框或网页),确定屏幕上各界面元素如何布局,屏幕中哪些信息是由软件系统自动提供的,哪些是由用户输入的,用户在屏幕上可施行哪些动作(例如选取菜单项、点击按钮等),软件系统应如何响应用户的界面动作,屏幕之间的跳转关系如何,界面元素如何与设计模型中负责业务逻辑处理的设计元素相衔接,等。界面设计必须充分考虑到用户的易用性需求,并遵循一些基本的原则以符合大多数用户的使用习惯。
(3) 体系结构设计。软件体系结构是指软件系统中主干性部件的组织形式,主要包括这些部件的职责定义,它们之间的接口定义、协作关系及协作行为。这些部件可以表现为子系统、构件或关键设计类。体系结构设计活动的内容包括:建立软件系统的顶层架构并进行适度精化,划分软件子系统、设置软构件,界定子系统、构件和关键设计类的职责,设计它们之间的接口和协作行为;标识支撑软件体系结构的技术基础设施(例如持久数据服务、事务管理、安全控制、并发控制、远程数据传输等)并研究其实现方案;针对软件的运行环境提出软件部署方案。有关软件体系结构设计的基本概念和预备知识详见“3.4 体系结构设计的预备知识”,读者目前不妨将软件体系结构视同软件产品“大厦”的“奠基”及“骨架设计”。
(4) 用例设计。针对需求分析模型中的每个用例,基于用户界面设计和体系结构设计中给出的设计元素,设计用例的软件实现方案。在此过程中,通过详细考察每个设计元素与其他设计元素协同完成用例功能的过程,可以更精确地定义这些设计元素。
(5) 子系统设计。在前述的体系结构设计活动中,仅设定了子系统的职责和对外接口,并未深究子系统的内部实现方案。这项工作留待子系统设计来完成。子系统设计的任务是,确定子系统内部的结构,即,设置包含于其中的更小粒度的子系统、构件和设计类,明确它们之间的协作关系,确保它们能够协同实现子系统接口规定的所有功能和行为。
(6) 构件设计。类似于子系统设计,构件设计是对体系结构设计活动中设定的构件的内部实现方案的设计,其任务是定义构件内部的设计元素及其协作方法。构件的内部元素既可以是类,也可以是粒度更细的构件。
(7) 类设计。围绕软件需求的实现,以上步骤(2)至步骤(6)设定了许多类,包括界面类、直接出现于体系结构中的关键设计类、实现用例功能或行为的设计类、子系统或构件中的设计类。类设计负责对这些类进行必要的设计精化,使之精细到可以提交软件实现的程度。具体的类设计工作包括:确定类的可见范围,定义类的操作和属性,精化类之间的关系,等。
(8) 数据模型设计。数据模型是指需要持久保存的数据条目的内容及条目之间的逻辑联系。有时,数据模型还包含持久存储机制提供的优化数据操作性能的特殊设施,例如,关系数据库中的存储过程[18]。数据模型设计活动的内容包括:确定前面获得的设计模型中哪些类的对象、这些对象的哪些属性需要持久保存;在设计模型与持久存储机制所能支持的数据组织方式(例如关系数据库中的表、关键字、外键等)之间进行映射;为提高数据存储、操作的性能而设计特定于持久存储机制的优化设施。目前及未来相当长一段时期内,关系数据库都将是最主流的持久存储机制,所以,下文仅讨论关系数据库环境下的数据模型设计。
(9) 设计整合。整合前面获得的所有设计模型,检查并消解它们之间的不一致性,剔除冗余性,以用例为导引构建设计模型中所有元素协力完成用例目标的完整视图,最终形成设计规格说明书。
(10) 设计评审。对设计规格说明进行评审和必要的修改,确保其满足软件需求,并具有明确、可行、高质量的特性。在软件项目的整个设计阶段临结束时,设计规格说明作为此活动的结果应该置入软件项目的基线库(详见xxx)。
(11) 总结。对本次子过程的活动及结果进行总结、评价,为所有参与者提供软件设计过程和中间产品的状态的直观描述,决定后续行动计划(启动下一设计子过程或进入软件构造阶段)。
以上活动按次序构成设计过程中单个子过程的工作流,见图1。
图1 用UML活动图表示的设计过程中单次迭代的工作流
类似于需求工程,设计过程中各活动之间的时序并非十分严格,在后续设计活动中发现的设计缺陷将导致引入该缺陷的前驱活动的返工,以彻底消除缺陷。
1.2 迭代式的设计过程模型
在设计过程中,迭代有两层含义。第一层含义是,针对给定的需求模型,通过多次从策划到总结的设计过程(见图1),得出足够精细的设计模型以供软件实现之用,见图2。在此迭代过程中,抽象级别逐次降低,细节不断丰富。迭代式设计的另一层含义是,以并不完整的需求模型为输入,展开前述意义下的迭代式设计,结果模型交由软件实现人员构建目标软件产品的原型或中间产品;在每次需求模型更新完成后,图2所示的设计过程再随之展开,直至获得最终的目标软件产品,见图3。
图2 针对固定需求的迭代式设计过程
图3 针对可变需求的迭代式设计过程
1.3 设计过程模型的裁剪
软件设计过程模型的裁剪方法与4.1.3小节所述的需求工程过程模型的裁剪方法相似,请读者参照§4.1.3自行推敲。
2 软件设计的策划
策划活动的任务是:决定本次迭代所处的设计抽象级别(即关注哪些问题,忽略何种细节),明确输入制品并使其处于就绪状态,定义本次迭代的目标、输出制品及其验收准则,确定覆盖本次迭代中多个阶段的全局性设计策略,制订工作计划。
策划活动的目的是使所有的设计活动参与者在上述所有方面达成共识。
2.1 策划活动的参与者
策划活动主要由项目软件经理和软件体系结构设计师(也称“软件架构师”)来完成。他们在策划过程中可能会求助于需求工程师以明确某项软件需求的精确含义,同时他们也需要与负责界面设计、用例实现方案设计、子系统设计、构件设计、类设计、数据模型设计的设计师协商设计策略、验收准则和工作计划。所有将参与本次设计迭代的软件设计师,以及项目软件经理、软件质量保证工程师、软件配置管理工程师(如果本次迭代会引起配置库的变化)都应参与策划活动的输出制品的评审,在评审过程中达成共识,并对与其相关的任务、进度约定做出承诺。此外,用户应参与界面设计模型的评审,软件实现工程师、软件测试工程师应参与最后一次设计迭代的输出制品的评审,以确认其可以立即用于软件构造和测试。
2.2 策划活动的进入准则与输入制品
策划活动的进入准则是:项目软件经理认为现有的需求模型或前次设计迭代得出的设计模型尚未精化到可供软件实现的程度。
首次设计迭代时,策划活动的输入制品是需求工程阶段生成的需求规格说明书,以及本单位或本项目的软件架构师事先制订的设计指南。设计指南中包含了针对具普遍性的设计问题的优选的设计方案。对于非首次设计迭代,输入制品还包括前次迭代形成的设计评审报告,其中可能含有本次迭代需要改正的设计缺陷的描述。
2.3 策划活动的步骤
设计策划活动的大致步骤如下:
(1) 确定本次迭代的目标和验收准则。目标与设计抽象级密切相关。因此,策划者在设计迭代之初必须决定本次迭代主要关注哪些骨干性、关键性问题,将哪些类别的细节性问题推迟至后续的设计迭代再解决。此外,设计目标也与策划者为本次迭代选定的工作范围有关。单次迭代可以仅仅针对目标软件系统的某个子系统而展开设计活动。见例1。
(2) 明确目标软件系统应遵循的技术标准或规范,研究软件需求规格说明书中的非功能性需求部分,针对重要的非功能性需求(例如性能、可靠性、可用性、可扩展性等),以及全局性设计问题(例如可复用性问题、多个设计目标发生冲突时的折衷问题等),制订设计策略或设计原则。此项工作主要由软件架构师负责。
(3) 重新审视项目风险管理计划,并研究在本次迭代过程中应实施哪些行为以降低或消除风险。
(4) 制订本次设计迭代的工作计划。见例2。
(5) 评审工作计划。
以上步骤(3)~(5)的内容与“4.2.4 策划活动的步骤”中的相应内容大体相当,从略。
2.4 策划活动的输出制品及出口准则
策划活动的输出制品为:本次迭代的工作计划、修改后的风险管理计划。
设计策划活动的出口准则与“4.2.6策划活动的出口准则”的内容大致相当,从略。
2.5 策划活动小结
参与设计策划活动的角色及其职责见表3。
表3 参与设计策划活动的角色及职责
角色 |
职责 |
软件架构师
|
(1) 与项目软件经理共同确定本次迭代的目标和验收准则; (2) 明确目标软件系统应遵循的技术标准或规范; (3) 制订设计策略及设计原则; (4) 制订本次迭代的工作计划。 |
项目软件经理 |
(1) 与软件架构师共同确定本次迭代的目标和验收准则; (2) 与软件架构师共同评估项目风险并研究在本次迭代中可采取的降低或消除风险的行动计划; (3) 评审本次迭代的工作计划。 |
用户 软件设计师 软件实现工程师 测试工程师 软件质量保证工程师 软件配置管理工程师 |
(1) 评审本次迭代的工作计划; (2) 对与自身相关的任务、进度约定做出承诺。 |
设计策划活动中的步骤可按次序组织为设计策划工作流,见图4。
图4 设计策划工作流
一旦工作计划确定,后续的工作就是按部就班地执行该计划。计划中最主要的事项依次是用户界面设计、体系结构设计、用例实现方案设计、子系统设计、构件设计、类设计、设计整合、设计评审和本次迭代总结。下面各节依次介绍完成这些事项的方法及工作流。
3 用户界面设计
用户界面设计的目标是,为用户使用目标软件系统以实现其所有业务需求而提供友好的人机交互界面。界面的友好性主要体现在易学、易用、简洁、美观等方面,详见“3.3.1 界面设计的一般原则”。
界面设计是提高软件易用性的关键环节,而软件易用性几乎又是所有具有人机交互界面的软件系统的关键质量要素。因此,对界面设计切不可等闲视之,必须反复推敲、精益求精。
3.1 界面设计活动的参与者
界面设计主要由界面设计师负责。其主要职责包括:设置每个界面中的所有界面元素,确定初步的界面布局,定义用户界面动作对软件系统中的设计元素的要求。软件架构师在此过程中为界面设计师提供必要的咨询、释疑。美工师负责界面的后期制作,其主要工作是:调整界面布局,确定界面的美学风格,包括色调、界面元素的表现形式等。限于篇幅,本书未能展开讨论界面美工师的工作方法,有兴趣的读者请参阅[3]。
用户、需求工程师、软件架构师、用例设计师应参与界面设计结果的评审。
3.2 界面设计活动的进入准则与输入制品
界面设计活动的进入准则如下:
(1) 由需求工程阶段负责生成的用例模型已基本确定。
(2) 参与界面设计的人员已具备必需的软件技术和人机工程学等方面的技能。
(3) 所有工作事项的职责已明确到人。
根据以上准则,界面设计并非要排在需求工程活动之后,其实在需求获取完成后就可以启动界面设计工作,但在界面设计过程中必须及时与需求分析的结果协调一致。
界面设计活动的输入制品包括:在需求获取阶段形成的界面情景板或界面原型、用例模型。在需求工程阶段结束时,需求规格说明书当然也将作为界面设计活动的输入制品。
3.3 界面设计活动的预备知识
在展开界面设计之前,界面设计者必须先掌握界面设计的一般原则,以及界面设计模型的表示方法。
3.3.1 界面设计的一般原则
为提高用户界面的友好度,界面设计必须遵循以下原则:
(1) 易理解性:界面上呈现的所有元素,包括文本信息、数据表示、状态呈现、菜单、按钮、超链等,贴近用户的业务领域,并且具有简洁、明确、自然、直观等特性。
(2) 易操作性:用户对软件系统的命令可以通过简单、直观的方式来完成;为提高用户的工作效率,界面应尽量减少用户的操作次数和输入信息量。
(3) 灵敏性:界面必须在合理的时间内对用户操作做出响应,对耗时较长的内部处理过程必须提供及时的进度反馈,保持用户与界面间的不间断的双向沟通。
(4) 一致性:为降低用户的记忆负担,界面应在整个软件系统范围内保持显示风格、操作方式的一致性并符合业界规范(例如用Ctrl+C快捷键来实现复制功能)。
(5) 容错性:界面设计应以降低用户的误操作机率为目标,但必须容忍用户的误操作,即:对所有可能造成损害的动作,必须在用户确认后才进行;允许用户对尽可能多的界面操作反悔(Undo);在用户误操作后系统具备适当的恢复能力。
(6) 人性化:在适当的时机出现用户恰好需要的帮助信息或建议;在任何情况下用户均能简易地理解软件系统的当前状态和响应信息,并能清晰地了解自己的操作行为的前因后果,不至因界面跳转而迷失;界面的布局和色彩应使用户感觉舒适、自然。
3.3.2 界面设计模型的表示
本书将界面中的窗口、对话框、网页统称为屏幕。界面设计的表示涉及两个方面:屏幕内容的表示、屏幕之间跳转关系的表示。
出现于屏幕中的界面元素有4种:
(1) 静态元素:与软件系统的运行状态无关、在任何情况下均没有变化的文本、图标(icon)、图形(graph)、图像(image)等。
(2) 动态元素:因当前用户及软件系统的运行状态而异,由软件系统根据业务逻辑自动呈现于屏幕中,且不允许用户修改的内容,包括不可编辑的文本、图标、图形、图像等。
(3) 用户输入元素:在屏幕上预留空位、由用户在界面操作中填写或选择的界面元素,包括可编辑的文本、单选钮(radio)、多选框(checkbox)、选择列表(select list)等。只要一个界面元素在某些情况下可供用户修改或选择,就应将其归入用户输入元素类,而非动态元素类。
(4) 用户命令元素:用户点击此类元素后位于界面后端的业务逻辑处理或界面刷新动作将被触发,其典型代表是按钮、菜单、超链等。
屏幕的瞬时快照可以表示为一张图,它描述了屏幕中静态元素的内容、其他3类界面元素在特定时间点上的视觉形式以及此时所有可见的界面元素的布局。当然,如果有界面设计工具,可以直接用工具设计屏幕外观,勿需另行绘图。
3.4 界面设计活动的步骤
界面设计的主要工作步骤如下:
(1) 研究需求工程阶段获得的用户界面情景板、快速原型、用例模型和可用性需求。此步骤的目的是:续用已有的界面设计的阶段性成果;确保后续的界面设计活动及结果与需求工程阶段的输出制品保持协调一致;提醒有关人员在界面设计过程中思索可用性需求的实现方法。
(2) 针对每个用例,依据用例中描述的软件系统与主要执行者之间的交互动作序列,找出该用例的主屏幕。主屏幕是指用户刚开始使用用例时系统呈现出来的画面,其他屏幕均直接或间接地源自主屏幕,并且用户在这些屏幕进行必要的操作后一般仍会回归主屏幕,用户在主屏幕上将花费比其他屏幕更多的使用时间。一般情况下,一个用例仅对应一个主屏幕,当然也不完全排除复杂用例对应多个主屏幕的情形。主屏幕的设计步骤大致如下:
¨ 构思主屏幕中的内容。用例描述中的交互动作部分、分析模型的交互图已经比较详尽地给出了用户向系统提供的信息,以及系统呈现给用户的信息。前者应表现为主屏幕的“用户输入元素”,后者则属于“动态元素”,对于它们的命名、提示信息则构成主屏幕上的“静态元素”。接下来,考虑用户在此屏幕上可以发起的业务操作,并将它们表现为“用户命令元素”。至此,最基本的界面已成雏形。
¨ 确定主屏幕对应的类的操作,这些操作包括在客户端完成的界面刷新动作和在后端完成的业务逻辑处理动作。它们来自四个方面:用户命令元素触发的操作;动态元素的值的改变导致的操作;初次呈现时屏幕的初始化操作;从其他屏幕跳转至主屏幕时要求主屏幕完成的操作。标识最后一种操作要求界面设计师综合考虑屏幕流的情况,所以也可等到步骤(3)再来填充或修改此类操作。
¨ 表示主屏幕。对可见的界面元素进行布局和必要的分组,绘制其外观图形后交由美工设计师进一步美化界面;以UML类图表示主屏幕的内容,以结构化自然语言阐述其操作,见图6。
(3) 仍然针对主屏幕所属的用例,对基于主屏幕的屏幕跳转流进行建模。屏幕流源于两种原因:单个屏幕的空间容量有限,不足以表现所有必要的界面元素;用户在主屏幕上的界面操作可能导出新的屏幕,以便在此屏幕上进行面向特定业务功能的界面交互。在屏幕流建模的过程中,要对作为跳转目标的从屏幕进行设计,其设计方法与前述步骤(2)基本相同。屏幕跳转流的表示方式主要是UML交互图和类图,前者表示特定应用场景下的屏幕跳转及跳转发生时的消息传递,后者借助有向关联关系表示在目标软件系统中屏幕之间所有可能发生的跳转及跳转的原因,它们的示例分别见图8和图9。在跳转不太复杂、也没有需要特别强调的应用场景的情况下,屏幕流的交互图表示可以省略,但类图表示是必需的。
说明:在实际的界面设计过程中,由于屏幕的数量往往相当大,即使采用分包绘制类图的方法,仍然很难将一个包中所有的界面类全部表示于单张类图,也没有必要如此,只需在类图中描绘主要的屏幕之间的跳转关系即可。
(4) 在整个软件系统范围内协调多个用例的屏幕和屏幕流:
¨ 研究多个用例对应的相似的或逻辑上相关的屏幕,探讨屏幕合并的可能性。
¨ 对已设计的屏幕进行调整,确保它们的界面风格一致化、操作方法一致化,并且使屏幕类的操作与屏幕跳转时发生的消息传递相吻合。
¨ 基于屏幕流的交互图和类图表示,整合原来分属于各个用例的屏幕流,使屏幕流在整个系统的范围内跳转顺畅。必要时,可以在子系统或粒度适当的软件模块的范围内绘制一张联合的屏幕交互图、屏幕类图,分别见图13和图14。
(5) 复核可用性需求的实现程度。将可用性需求分解至屏幕类或其操作,它们将成为屏幕类的实现约束。研究屏幕类的实现能否切实满足可用性需求的要求。必要时,以可用性需求为目标,重新调整屏幕类的设计。必要及可能时,在可用性需求项与屏幕或屏幕集之间建立追溯关系,见表4。
(6) 构造界面原型。借助界面原型构造工具,基于迄今获得的界面设计结果构造界面原型。此动作不是必需的,但原型可显著提升用户对界面设计模型的评审工作的有效性。
(7) 评审界面设计模型。用户、需求工程师、软件架构师、用例设计师应参与界面设计模型的评审。评审的主要关注点如下:
¨ 用户和需求工程师,尤其是前者,必须认可屏幕的静态图示、屏幕交互图及界面原型。
¨ 屏幕及屏幕流应该覆盖所有用例对交互界面的要求。
¨ 屏幕及屏幕流应该与用例中的所有交互动作序列在逻辑上协调一致。
¨ 界面设计模型符合本项目采用的界面设计原则、指南或规范的要求。
¨ 屏幕交互图和屏幕类图保持一致,即,对屏幕交互图中的每条消息,在屏幕类图中均存在一条相应的跳转路径;屏幕跳转时源屏幕到目标屏幕的消息均须对应目标屏幕类中的某项操作。
¨ 屏幕的静态图示与屏幕交互图和屏幕类图保持一致,即,对屏幕中的每项动态内容,在其屏幕类中均存在相应的属性;对屏幕中的每个用户输入元素,要么在其屏幕类中存在相应的属性,要么在相应的输入表格类中存在相应的属性;对屏幕中的每个用户命令元素,在其屏幕类或相应的输入表格类中存在相应的操作;对屏幕中的每个用户命令元素,其导致的屏幕跳转在屏幕交互图中存在相应的表示。
¨ 在约束是可实现的前提下,界面设计模型可完整地实现可用性需求。
¨ 可用性需求对界面设计模型的约束是可实现的。
3.5 界面设计活动的输出制品及出口准则
界面设计活动的输出制品包括:界面设计模型(屏幕的静态图示、屏幕交互图、屏幕类图),界面原型(如果有的话),可用性需求追踪表。
界面设计活动的出口准则是:界面设计模型通过评审。
3.6 界面设计活动小结
参与界面设计活动的角色及其职责见表5。
表5 参与界面设计活动的角色及职责
角色 |
职责 |
界面设计师
|
(1) 设计屏幕及屏幕流,构造界面设计模型; (2) 构造界面原型; (3) 构造可用性需求追踪表。 |
美工师 |
对界面进行美工设计。 |
软件架构师 |
(1) 为界面设计师提供必要的咨询、释疑; (2) 评审界面设计模型。 |
用户 需求工程师 用例设计师 |
评审界面设计模型。 |
界面设计活动中的步骤可按次序组织为界面设计工作流,见图15。
图15 界面设计工作流
4 体系结构设计
体系结构设计的目标是建立软件系统的体系结构,有时也称“顶层架构”。这种架构既要明确定义软件各子系统、关键构件、关键类的职责划分及协作关系,同时也要描绘它们在物理运行环境下的部署模型;此外,顶层架构还必须针对软件系统全局性、基础性的技术问题给出技术解决方案,这种方案往往构成目标软件系统的体系结构的技术基础设施。
自顶下向,逐步精化是一种广泛采用、行之有效的软件设计原则,软件设计往往始于体系结构设计。因此,体系结构设计对整个目标软件系统起着初始塑形的作用,它对软件需求的实现,包括功能性需求以及性能、可扩展性、可维护性等非功能性需求的实现都具有决定性意义。
4.1 体系结构设计活动的参与者
体系结构设计活动主要由软件架构师负责完成。项目软件经理、其他软件设计人员(包括界面设计师、用例设计师、子系统及构件设计师、详细设计师、数据模型设计师)以及软件质量保证工程师应参与体系结构设计活动的输出制品的评审。
4.2 体系结构设计活动的进入准则与输入制品
体系结构设计活动的进入准则是:
(1) 需求分析模型已就绪。
(2) 本次设计迭代的策划活动已完成。
4.3 体系结构设计的预备知识
在展开体系结构设计之前,必须先了解软件设计模式,尤其是体系结构模式的有关知识,以便有效地借鉴全世界众多软件架构师在长期的实践过程中积累的宝贵的设计经验。
4.3.1 设计模式
设计模式是指,以设计复用为目的,采用一种良好定义的、正规的、一致的方式记录的软件设计经验。每条模式关注在一般或特定设计环境中可能重复出现的设计问题,并给出经过充分实践考验的软件解决方案。通常,一条设计模式包含以下内容:
(1) 设计模式的名称。此名称应能概观地反映模式所蕴含的设计经验,并尽量体现它与业界广泛采用的已有模式之间的关系。
(2) 问题。描述模式所解决的设计问题,包括问题的背景。
(3) 施用条件。描述在何种条件下才推荐使用该模式来解决上述问题,以及在使用本模式之前必须考虑的约束条件。
(4) 解决方案。这是设计模式的主体部分,它描述问题的软件解决方案。
(5) 效果。描述上述解决方案导致的正面及负面的设计效果。
(6) 示例代码。以特定的程序设计语言或类程序设计语言给出应用本模式的示例代码。
(7) 关联模式。说明本模式继承或扩展了哪些模式,与哪些模式有关联。
对设计模式分类有多种视角:
(1) 从模式所提供的解决方案的抽象程度来看,模式自高至低可依次划分为:
¨ 体系结构设计模式:面向整个软件系统或规模较大的软件子系统,给出抽象程度较高的结构化组织方式。它提供一些预定义的子系统或者构件,规定其职责,描述它们之间相互关系、协作方式的规则或指南。
¨ 软件子系统或构件设计模式:面向中等规模的软件子系统或构件,以独立于程序设计语言的方式,给出其内部的软件元素(粒度更小、抽象级别更低的子系统或构件)的结构化组织方式。如果此类设计模式采用面向对象方式给出其解决方案,那么,方案中的软件元素往往是类,解决方案除规定每个类的职责外,还会以UML类图表示类之间的关系,以UML交互图表示类之间的协作途径。
¨ 面向软件实现的设计模式:针对软件子系统或构件中的某个特定问题,描述如何利用特定的程序设计语言的具体特征来解决此问题。此类模式不影响软件结构。
(2) 从模式所解决的设计问题的类别来看,模式可划分为:
¨ 创建型模式:专门解决复杂对象的创建问题。
¨ 结构型模式:将软件系统、子系统或构件分解为粒度更小的软件元素,规定其职责和协作方式。
¨ 行为型模式:不仅描述软件系统、子系统或构件的内部结构,更强调位于结构中的软件元素在协同解决问题时的通信及控制流模式。
¨ 分布型模式:为构件分布于网络中不同计算机的软件系统提供系统结构和远程互操作方法。
¨ 适应性模式:专门针对软件系统、子系统或构件的扩展、改进、演进、变更等设计问题提供软件解决方案。
¨ 访问控制模式:专门针对构件、软件服务或共享资源的访问控制问题提供软件解决方案。
由于软件设计问题的多样性,以上列表并未穷尽所有的模式类别。
(3) 从模式所基于的具体计算平台的类别来看,模式可划分为:
¨ 独立于计算平台的设计模式。
¨ J2EE设计模式[7]:描述充分利用J2EE平台的优势,回避其缺陷,从而改善J2EE应用软件的质量属性的设计经验。
¨ NET设计模式[10]。等等。
为使读者对设计模式有更具体的理解,下面举例说明设计模式对于软件设计师的意义和价值。
4.3.2 体系结构模式
体系结构模式是专门针对体系结构设计问题的设计模式,是有关软件体系结构设计的经验结晶。它对于软件架构师具有重要的借鉴作用。
管道和过滤器模式
该模式将软件系统的功能实现为一系列的处理步骤,每个步骤封装在一个过滤器构件中。相邻过滤器之间以管道连接,一个过滤器的输出数据借助管道流向后续过滤器,作为其输入数据。整个软件系统的输入由数据源(data source)提供,它通过管道与过滤器相连。软件系统的最终输出由源自某个过滤器的管道流向数据汇(data sink)。典型的数据源和数据汇包括数据库、文件、其他软件系统、物理设备等。一个软件系统可以有多个数据源、多个数据汇。过滤器、数据源、数据汇与管道之间的协作方式有以下几种:
¨ 过滤器以循环方式工作,不断地从管道中提取输入数据,并将其输出数据压入管道。此种过滤器称为主动过滤器。
¨ 管道将输入数据压入位于其目标端的过滤器,过滤器被动地等待输入数据。
¨ 管道负责提取位于其源端的过滤器的输出数据。
如果管道连接的两端均为主动过滤器,那么管道必须负责它们之间的同步,典型的同步方法是先进先出缓冲器。如果管道的一端为主动过滤器,另一端为被动过滤器,那么管道的数据流转功能可通过前者直接调用后者来实现。此种实现方法虽然简洁、高效,但加大了两个过滤器之间的耦合度,使过滤器重组变得非常困难。请读者自行思索使位于管道两端的主动、被动过滤器松耦合的管道实现方法,以及连接两个被动过滤器的管道的实现方法,见习题xxx。
采用管道和过滤器模式,可以通过升级、更换部分过滤器构件,以及处理步骤的重组,来实现软件系统的扩展和进化。但此模式仅适合于采用批处理方式的软件系统,不适合于交互式、事件驱动式系统。
图19 管道和过滤器模式示意图
分层模式
该模式将软件系统按照抽象级别逐次递增或递减的顺序划分为若干层次,每层由一些抽象级别相同的构件组成,见图20。在严格的分层体系结构下,每层的构件仅为紧邻其上的抽象级别更高的层次提供服务,并且它们仅使用其紧邻下层提供的服务;在稍松散的分层体系结构中,服务提供者和接受者可以跨越中间层,但前者一定位于比后者更高的抽象层次。一般而言,顶层直接面向用户提供软件系统的交互界面,底层则负责提供基础性、公共性的技术服务,它比较接近于硬件计算环境、操作系统、数据库管理系统,或者由J2EE/.NET中间件构成的逻辑计算平台,中间层的抽象级别介乎二者之间。
层次之间的连接有两种形态:
¨ 高层构件向低层构件发出服务请求,低层构件在计算完成后向请求者发送服务应答。在此过程中,低层构件可能向更低层构件发送抽象级别更低、粒度更细的服务请求。
¨ 低层构件在主动探测或被动获知计算环境的变化事件后通知高层构件,这种通知链可能一直延伸到最高层以便软件系统向用户报告,也可能中止于某个中间层次。
每个层次对上层的服务接口有两种组织方式:层次中的每个提供服务的构件公开其接口;将这些接口封装于层次的内部,每个层次提供统一的、整合的服务接口。对上层服务请求者而言,前一种方式更直接,服务提供者所在层次的透明度也较高,但后一种方式降低了两个层次之间的耦合度。
合理地确立一系列抽象级别是分层体系结构设计的关键。在此前提下,分层体系结构模式可具有以下正面效应:
¨ 松耦合:通过软件层次的划分和层间接口的规整有效降低整个软件系统的耦合度,强化软件系统各构件之间的依赖关系的局部化程度。
¨ 可替换性:一个层次可以被实现了同样的对外服务接口的层次所替换;即便接口有所变化,层次替换的影响传播范围也仅限于直接使用该层服务的上层构件。
¨ 可复用性:具有良好定义的抽象级别和对外服务接口的层次可以在不同的上下文环境中实现复用。
¨ 标准化:定义清晰、广为接受的抽象级别可望促进标准化构件和标准化接口的开发,正如ISO七层网络协议模型、POSIX接口标准、Java虚拟机(JVM)标准在各自领域的标准化进程中发挥了关键性作用。
分层体系结构因上述特色而广受欢迎,但在应用过程中也需注意避免以下负面效应:
¨ 性能开销:高层功能可能需要逐层调用下层服务,返回值、报错信息又需逐级上传,这种过程一般会比直接实现高层功能更耗时。如果低层服务还完成了最初的服务请求者所不需要的冗余功能,那么性能损耗就会更严重。
图20 分层模式示意图
黑板模式
该模式将软件系统划分为黑板、知识源和控制器三类构件,见图21。黑板负责保存问题求解过程中的状态数据,并提供这些数据的读写服务;知识源负责根据黑板中存储的问题求解状态评价其自身的可应用性,进行部分问题求解工作,并将此工作的结果数据写入黑板;控制器负责监视黑板中不断更新的状态数据,安排(多个)知识源的活动。
采用黑板模式的软件系统的典型动作过程如下:
¨ 控制构件通过观察黑板中的状态数据决定哪些知识源对后续的问题求解可能有所贡献,然后调用这些知识源的评价功能以进一步选取参与下一步求解活动的知识源。
¨ 被选中的知识源基于黑板中的状态数据将问题求解工作向前推进一步,并根据此步骤的结果更新黑板中的状态数据。
¨ 控制构件不断重复上述控制过程,直至获得满意或比较满意的结果。
黑板模式适合于没有确定的求解方法的复杂问题。其正面设计效应有:
¨ 黑板模式的知识源和控制构件可灵活更换、升级,所以它支持软件设计师采用不同的知识源、不同的控制算法来试验各种问题求解方法。
¨ 由于知识源之间几乎没有互操作,知识源与控制构件和黑板之间均通过良好定义的接口进行交互,知识源的复用性较好。
¨ 由于知识源的每个问题求解动作都是探索性的,允许失败和试错,所以采用此模式的软件系统具有较好的容错性和健壮性。
采用黑板模式的负面效应是:
¨ 问题求解性能较低、有时甚至无法预测求解时间。
¨ 不能确保获得最优解。
¨ 知识源和控制器两种构件的开发相当困难。
¨ 问题求解路径并不确定,造成软件测试方面的困难。
图21 黑板模式示意图
模型-视图-控制器模式
该模式又称MVC模式。它将软件系统划分为三种构件:模型、视图和控制器,见图22。模型构件负责存储所有的业务数据并提供业务逻辑处理功能;一旦业务数据有变化,模型构件负责将变化情况通知视图构件以便其及时反映新的变化。视图构件负责向用户呈现模型中的数据,在接获模型的数据变化通知后从模型中获取新数据并更新视图。单个模型可对应多个视图,以便给用户呈现不同的数据表现形式。例如,同一组数据既可以呈现为二维表,也可以表现为直方图或饼图。视图还负责接受用户的界面输入(例如鼠标事件、键盘输入等),并将其转换为内部事件传递给控制器,控制器再将此类事件转换为对模型的业务逻辑处理请求,或者对视图的展示请求。控制器在接获模型的业务逻辑处理结果后负责选择适当的视图作为软件系统对用户的界面动作的响应。
采用MVC模式的软件系统的典型运作流程如下:
¨ 创建视图,视图对象从模型中获取数据并呈现在用户界面上。
¨ 视图接受用户的界面动作,并将其转换为内部事件传递给控制器。
¨ 控制器将来自用户界面的事件转换为对模型的业务逻辑处理功能的调用。
¨ 模型进行业务逻辑处理,将处理结果回送给控制器,必要时还需将业务数据已经发生变化的事件通知给所有现行视图。
¨ 控制器根据模型的处理结果创建新的视图、选择其他视图或维持原有视图;所有视图在接获来自模型的业务数据变化通知后向模型查询新的数据,并据此更新视图。
MVC模式将模型与视图分离以支持同一模型的多种展示形式,界面的式样和观感可动态切换、动态插拔而不影响模型;将视图与控制器分离以支持在软件运行时根据业务逻辑处理结果选取最适当的视图;将模型与控制器分离以支持从用户界面动作到业务处理行为之间的映射的可配置性。此外,模型与视图之间的变更一-通知机制确保了视图与业务数据的适时同步。正是由于这些特性,MVC模式特别适合于远程分布式应用(包括WEB和WAP应用软件),此种应用软件已经成为当前软件产业界的主流。但是,MVC也有一些负面的设计效应,包括由于模型、视图、控制器三者之间的分离而导致的开发复杂性和运行时的性能开销,好在目前基于MVC模式的软件框架(例如Spring Framework[8])以及有关的设计模式[7]有效地缓解了这一问题。
图22 MVC模式示意图
4.4 体系结构设计的步骤
软件体系结构设计的主要步骤如下:
(1) 开发初始的软件顶层架构。
(2) 搜索并选取可用的设计资产。
(3) 研究公共的基础性软件技术问题,设计技术支撑方案。
(4) 确定设计元素。
(5) 开发软件部署模型。
(6) 设计并发机制。
(7) 构建软件体系结构模型。
(8) 评审软件体系结构模型。
上述步骤(1)~(4)可能需要反复迭代进行。在非首次迭代的过程中,这些步骤并不一定全部出现,它们之间也不存在严格的时序关系。
4.4.1 开发初始的顶层架构
参考业界已有的软件体系结构模式,结合当前软件项目的特殊要求,尤其是非功能性需求,选取合适的模式。
4.4.2 搜索并选取可用的设计资产
设计资产包括相同或相关业务领域中的模块、子系统、构件、框架[11]、类库、应用软件系统、设计模式等。“可用”的设计资产是指在当前项目中直接可供复用或借鉴的设计资产。例如,针对案例1,可以直接借用现成的学籍管理系统和教师信息管理系统来实现对用户身份的认证,借用现成的计费系统来确定学生应缴的选课费用,见图27。
对于必须与当前软件系统交互的外部系统(软件或者物理设备),必须清晰地定义它们与当前软件系统之间的交互接口,包括数据交换的格式、互操作协议等。对于虽不能直接使用,但具有复用潜力的设计资产,架构师应考虑采用适配器、接口重构等方法将其引入当前软件系统的体系结构之中。
4.4.3 设计技术支撑方案
在许多软件项目中,应用功能往往都需要一组技术支撑机制为其提供服务。例如,对分布式应用软件(包括电子商务应用、企业ERP系统等)而言,需要数据持久存储服务、安全控制服务、分布式事务管理服务、可靠消息服务等。这些技术支撑设施并非业务需求的直接组成部分,但形态各异的业务处理功能全都有赖于它们提供的公共技术服务。让每个业务功能的设计者直接面对裸机、基本操作系统或基本网络环境来完成软件设计方案,那是不可思议的。
技术支撑方案应该为多个用例的软件实现提供技术服务,所以,它应该成为整个目标软件系统中全局性的公共技术平台。当用户需求发生变化时,技术支撑方案应具有良好的稳定性。这就要求软件设计者选用开放性和可扩充性较好的技术支撑方案。如果目标软件系统的顶层架构采用分层方式,那么,技术支撑方案应该位于层次结构中的较低层次。
技术支撑方案的设计一方面取决于目标软件对公共技术服务的需求,另一方面取决于架构师对目标软件的基础技术平台所能提供的技术服务的把握和选取。例如,对分布式应用而言,架构师必须了解分布构件技术、基于应用服务器的软件开发技术等。下面分别以数据持久存储服务和安全控制服务为例说明技术支撑方案的设计方法及其设计结果与初始的软件顶层架构的融合方法。
数据持久存储服务
数据持久存储是指,在目标软件系统结束一次运行之后,其产生的部分数据能够留存于计算机系统的存储介质中,以供本软件下次运行时使用,或者供其他软件系统使用。数据持久存储服务的功能包括数据的持久存储、查询(选择性读取)、更新、删除已存储的部分数据,等。设置数据持久存储服务的目的是,将目标软件系统中依赖于系统运行环境的数据存取部分与其他部分相分离。数据存取通过一般的数据管理系统(如文件系统、关系数据库或面向对象数据库)实现,实现细节因数据存储介质的种类而异,但这些细节被集中在数据持久存储服务中,系统的其他部分只需访问数据持久服务即可,与存储介质的种类、数据在介质中的组织方式、数据存储的实现方法无关。这样,一旦存储介质发生变化或者存储格式发生变化(例如,从文件系统改变为关系数据库,从一种数据库改变为另一种数据库,数据库表格结构变化,等),仅修改数据持久存储服务即可适应这些变化,目标软件系统的其余部分基本不需修改。所以,设置相对独立的数据持久存储服务不仅简化了后续的软件设计、编码和测试的过程,也有利于软件的扩充、移植和维护。
一般将数据持久存储服务实现为一个构件,其接口定义通常包括一些基本的、通用的数据持久操作,例如存储、查询、更新、删除等,见例5。当然,如果架构师希望该服务承担的职责非常多,也可以考虑将其实现为一个子系统,其典型结构如图24所示。该子系统通常包含上述构件,并利用它为一些更加接近业务逻辑处理的构件提供基础性数据持久服务。
图24 数据持久存储服务子系统的结构
4.4.4 确定设计元素
设计元素包括子系统、构件、设计类三种。本步骤以用例分析中提取的分析类和界面设计中给出的界面类为基础,以软件需求的实现为目标,探索如何将分析类和界面类组织为设计元素,并进一步研究这些设计元素之间的职责如何划分,它们之间如何协同工作。本步骤只要确定设计元素的职责和相互协作关系即可,并不需要给出设计元素的内部结构和实现途径,此项工作留待“6 子系统设计”、“7 构件设计”、“8 类设计”再完成。由于设计元素的设置并无可供机械遵循的定式,架构师必须善用设计模式和自己的设计经验。
确定设计元素的主要子步骤如下:
STEP1:确定子系统及其接口
STEP2:确定构件及其接口
STEP3:确定关键设计类
STEP4:整合设计元素
4.4.5 开发软件部署模型
软件部署模型负责展示软件中各子系统、构件在哪些计算结点上运行,以及这些结点之间的网络连接方式。它反映了软件系统的网络运行环境和物理分布状况。对于单机软件,软件系统全都部署于一台计算设备之上,这种软件的部署模型非常简单,不需单独示出。但是,现今及未来的绝大多数应用软件均为网络分布式软件,有必要在设计之初即勾勒出其部署模型,以利于后续的设计、编码、测试和维护。
4.4.6 设计并发机制
并发是提高软件效率的重要手段。不仅如此,当用户界面与业务逻辑处理处于同一台计算机时,并发也是系统在进行耗时很长的业务处理时用户界面保持活跃并与用户及时交互的唯一方法。本步骤的主要工作是,针对目标软件系统的性能需求(包括界面的灵敏性需求),将一些可以并行执行的操作序列划分成不同的任务,明确这些任务在并发执行过程中可能的同步点,并研究并发任务在目标软件系统所基于的计算平台上的实现方法。正如“2.6 活动图”所述,UML活动图非常适合于表示并发任务的划分、任务之间的并发与同步,见图33。
4.4.7 构建体系结构模型
本步骤对前述步骤(4.4.1~4.4.6)的工作成果进行整理、改进,以正式文档的形式完整地描述目标软件系统的体系结构模型。软件体系结构文档主要包含以下内容:
(1) 引言:描述本文档的目的、内容提要、引用的参考文档(例如需求规格说明书、有关软件体系结构模式的技术文献等)。
(2) 体系结构概述:宏观地描述软件系统中各子系统、构件、设计类的职责、协作关系,设计体系结构时遵循的主要原则,关键的设计决策及其依据,本文档所定义的体系结构的特色、优势及可能的缺陷。
(3) 需求视图:概述或引用对软件体系结构有重要影响的功能性和非功能性需求,必要时说明它们与体系结构或设计决策之间的关系。
(4) 逻辑视图:详细阐述软件系统的分解情况、其中各子系统、构件、关键设计类的职责及协作关系。可以采用扩展的UML包图描述软件系统的分解情况,用扩充的UML类图表示各设计元素之间的静态逻辑关系。设计元素的职责一般用结构化文本描述,动态协作关系一般用UML交互图表示。
(5) 进程视图:描述软件系统中的并发处理及必要的同步措施。
(6) 实施视图:描述在软件项目的后续开发过程中各类文档、程序代码的组织结构,例如Java包、目录树等,见附录xxx。
(7) 部署视图:描述软件的部署模型。
(8) 应用指南:描述在后续的设计过程中使用本文档所定义的体系结构的重要注意事项,以便扬长避短,切实满足需求规格说明书中的功能性和非功能性需求,避免体系结构的缺陷损害目标软件的质量。
4.4.8 评审体系结构模型
项目软件经理、需求工程师、软件架构师、将参与用例设计、子系统设计、构件设计、类设计的软件设计师应参与体系结构模型的评审。评审的主要关注点如下:
¨ 体系结构是否能够满足软件需求,以及怎样满足软件需求。这里的需求包括功能性需求与非功能性需求。
¨ 当异常或者临界条件出现时,体系结构是否能够以令人满意的方式运作。
¨ 体系结构的详略程度是否恰当,既不至过于细化而束缚后续设计的自由度,也不至过于粗放而放任后续设计背离软件需求,或者使后续设计无所适从。
¨ 体系结构是否存在可行性方面的风险。
4.5 体系结构设计活动的输出制品及出口准则
体系结构设计活动的输出制品包括:体系结构模型,“需求-设计元素”追踪表。
体系结构设计活动的出口准则是:体系结构模型通过评审。
4.6 体系结构设计活动小结
参与体系结构设计活动的角色及其职责见表7。
表7 参与体系结构设计活动的角色及职责
角色 |
职责 |
软件架构师
|
(1) 设计软件的体系结构,构建体系结构模型; (2) 构造“需求-设计元素”追踪表。 |
项目软件经理 界面设计师 用例设计师 子系统及构件设计师 详细设计师 数据模型设计师 软件质量保证工程师 |
评审体系结构模型。 |
体系结构设计活动中的步骤可按次序组织为体系结构设计工作流,见图34。
图34 体系结构设计工作流
5 用例设计
用例设计是指,针对需求分析模型中的每个用例,设计其软件实现方案。用例设计活动的目标是确保界面设计模型、体系结构模型与软件需求的符合性。用例设计的主要任务是:
(1) 采用体系结构设计中确定的软件设计元素(包括子系统、构件、关键设计类),以及用户界面设计中确定的界面类(包括屏幕类及输入表格类),完整地实现每个用例要求的业务处理功能,即其中的每种交互动作序列。
(2) 通过详细考察每个设计元素(含界面类,下同)与其协作者之间的协作关系,以求更精确地定义这些设计元素。
在软件设计过程中引入用例设计环节,是为了在子系统设计、构件设计、类设计、数据模型设计尚未全面铺开之前,以软件需求为准则来检验界面设计模型和体系结构模型的符合性,及早发现不一致性并立即改正。
1 用例设计活动的参与者
用例设计由用例设计师负责。软件架构师在此过程中为用例设计师提供必要的咨询、释疑。
2 用例设计活动的进入准则与输入制品
用例设计活动的进入准则是:
(1) 分析模型、非功能性需求描述、体系结构模型、界面设计已基本完成。
(2) 所有工作事项的职责已明确到人。对于中、大型的软件系统而言,用例数量较多,可设置多名用例设计师。此时,应该按照某种逻辑分组规则,将用例划分为若干组,每名设计师负责若干组用例。此外,应指定一名设计师负责所有用例的实现方案的整合(见“3 用例设计活动的步骤”)。
用例设计活动的主要输入制品是需求分析阶段生成的分析模型,尤其是分析模型中的交互图。本质上,需求分析阶段中的用例分析活动(包括提取分析类、构造交互图、构造分析类图,见“4.4.4分析活动的步骤”)与本步骤具有相似的目标,都是为了提出用例所要求的业务处理功能的软件解决方案,但是,二者的关注点和详略程度有很大差异:分析阶段关注用例功能的精确描述(“做什么”)以及实现这些功能的职责如何在分析类之间划分,详略程度只需要满足“将用例功能描述清楚”的要求即可;设计阶段关注“以何种最恰当的方式”、“怎么做”,以便实现分析模型的交互图所描述的用例功能,其详略程度必须满足“可直接提交编程实现”的要求。
用例设计活动的另一重要输入制品是需求规格说明书中的非功能性需求,因为用例实现方案不仅要满足用例的功能要求,也要满足相关的非功能性需求,或者对非功能性需求的实现做出贡献。
3 用例设计活动的步骤
用例设计的主要工作步骤如下:
(1) 为了逐个用例地给出以UML交互图表示的软件实现方案,针对每个用例展开以下工作:
(1.1) 研究需求工程阶段获得的用例的UML交互图表示、与当前待设计的用例相关的界面设计模型、体系结构模型、非功能性需求中的某些部分。前者是生成本步骤的主要输出制品――基于设计元素的用例实现方案――的主要基础。界面设计模型和体系结构模型是用例实现方案的参与者――设计元素――的主要来源,设计元素包括子系统、构件、用于业务逻辑处理的类、界面设计模型中的屏幕类和输入表格类。研究非功能性需求是为了提醒设计师在用例实现方案的设计过程中思索这些需求的实现方法。
(1.2) 给出基于设计元素实现用例中所有交互动作序列的方案。如前所述,在需求工程的用例分析阶段,已经给出了基于分析类的用例实现方案。只要明确了分析类的职责与设计元素的操作之间的对应关系,就不难将分析阶段生成的UML交互图转换成设计阶段需要的交互图。但是,分析类与设计元素之间的对应关系多种多样,可能出现的情形包括:
¨ 一个分析类的一项职责由一个设计元素的单项操作完整的实现。这种情形最简单,从分析模型中的交互图到设计模型中的交互图的变换方法见图35(a)。
¨ 一个分析类的一项职责由一个设计元素的多项操作来实现。这种情形的交互图变换方法见图35(b)。
¨ 一个分析类的一项职责由多个设计元素协同完成。此时用例设计师应考虑如何将分析类的职责分配给这些设计元素,以及这些设计元素如何通过必要的消息传递来进行协作。见图35(c)。
¨ 对于在分析模型的交互图中出现的执行者,如果该执行者由软件系统的用户扮演,那么在设计模型中其对应的设计元素应该为界面设计模型的界面类,其发送消息、响应消息的职责应该由界面类的操作来实现,因为用户是通过界面与软件系统交互的;如果该执行者是外部设备或外部软件系统,那么应该将其替换为“4.4.4 确定设计元素”之“STEP1:确定子系统及其接口”中设置的子系统。
(a) 说明:B’是对应于分析类B的设计元素,msg’是msg在设计模型中的对应物。
(b) 说明:分析类B处理msg的职责被分解为设计元素B’中处理msg1’,…,msgn’的操作
(c) 说明:在设计模型中,消息msg’触发分析类职责的执行
图35 从分析模型中用例的顺序图表示到设计模型中用例实现方案的变换方法
当然,从分析模型中的交互图生成设计模型中的交互图决非简单的语法替换,软件设计师必须确保后者能够准确、完整地实现前者所规定的业务逻辑处理功能及行为。在此基础上,设计师应当选用尽可能简单、高效的实现方法,并思考实现方法如何满足相关的非功能性需求。此时,设计模式可以发挥很好的作用。因此,软件设计师必须熟练掌握有关的设计模式知识,并在此步骤中灵活运用。
(1.3) 构造设计类图。基于前面给出的用例实现方案的交互图表示,将分析模型中的分析类图变换为设计模型中的设计类图。设计类图中的结点为各种设计元素,它们的类别(子系统、构件、业务逻辑类、界面类)采用不同的UML构造型或者不同的图元符号来表示,见图31。依据上述交互图推导出设计类图的过程类似于从分析模型之交互图推导出分析类图的过程,参见“4.4.4.6构造分析类图”,这里不再赘述。不同的是,这里的推导过程同时也是从分析类图到设计类图的精化过程,应该复用分析类图中可以续用的机制,并且确保设计类图相对于分析类图的逻辑符合性。
(1.4) 设计测试用例(参见xxx)。基于需求模型中针对用例给出的测试场景(见“4.3.4 精化用例”),设计具体的测试用例。测试用例可以有多个,每个测试用例专门针对用例目标的某个方面。按照软件测试的一般原则,应该尽可能完整地覆盖用例目标的各个方面,并且采用尽可能少的测试用例来实现这种覆盖。这些测试用例将用于软件集成测试和验收测试,它们应具有如下功效:如果最终的目标软件系统能够顺利通过测试,就可以在一定程度上说明软件系统已经正确地实现了用例目标。
(2) 整合用例实现方案。在针对每个用例完成上述工作后,用例设计师必须从全局和整体的高度整合所有的用例实现方案,具体工作包括:
¨ 将具有相同或相似职责的多个设计元素整合为一个。
¨ 将具有相同或相似功能的多个操作整合为一个。
¨ 采用继承或代理机制(见“1.4.3 面向对象的复用”)对设计元素中的公共操作进行抽象。
¨ 确保所有用例实现方案在同一软件系统中和谐共存,无逻辑冲突。
¨ 根据上述调整相应地修改前述的交互图和设计类图。
(3) 复核软件需求的实现程度。将有关的软件需求分解至设计元素或其操作,它们将成为设计元素的实现约束。必要时,以软件需求为目标,重新调整用例实现方案和设计类图。在此过程中,尤其要充分关注非功能性需求。必要及可能时,在软件需求与设计元素或设计元素集之间建立追溯关系。
4 用例设计活动的输出制品及出口准则
用例设计活动输出制品包括:用例实现方案(交互图、设计类图)、测试用例、非功能性需求追踪表。
用例设计活动的出口准则是:用例实现方案是完整、可行的。
5 用例设计活动小结
参与用例设计活动的角色及其职责见表8。
表8 参与用例设计活动的角色及职责
角色 |
职责 |
用例设计师
|
(1) 设计用例实现方案; (2) 设计测试用例; (3) 构造“需求-设计元素”追踪表。 |
软件架构师 |
为用例设计提供必要的咨询、释疑。 |
用例设计活动中的步骤可按次序组织为用例设计工作流,见图39。
图39 用例设计工作流
6 子系统设计
子系统设计的目标是,确定子系统内部的结构,即,设置包含于其中的(更小粒度的)子系统、构件和设计类,明确它们之间的协作关系,确保它们能够协同实现迄今为止获得的子系统服务提供接口规定的所有功能和行为。
6.1 子系统设计活动的参与者
一般情况下,子系统的设计由子系统设计师负责,软件架构师在此过程中为其提供必要的咨询、释疑。如果子系统的规模较大,可以由软件架构师在完成整个系统的体系结构设计后继续负责子系统的设计。
6.2 子系统设计活动的进入准则与输入制品
子系统设计活动的进入准则是:
(1) 体系结构设计、用例设计已基本完成。
(2) 职责明确。如果有多名设计师参与子系统设计,必须事先明确他们各自的分工和职责。
子系统设计活动的输入制品包括:体系结构模型,与当前待设计的子系统对应的分析模型中的状态图/活动图(如果有的话)、相关的非功能性需求。
6.3 子系统设计活动的步骤
子系统设计的主要工作步骤如下:
(1) 研究相关的非功能性需求,以及分析模型中相关的状态图、活动图(如果存在的话)。前者提醒子系统设计师思考相关的非功能需求的实现方法,状态图和活动图帮助设计师理解当前子系统的运作机理。
(2) 将子系统的服务提供接口中规定的职责分配给子系统中的软件设计元素。子系统一般对应分析模型中一个或一些较复杂的分析类。为了完成其职责,需要在子系统中设置构件、设计类,有时甚至需要设置当前子系统的子系统。设置这些软件设计元素的目的是为了使软件实现人员能够可行地、精确地实现当前子系统,因此需要确定它们的职责和协作关系,通过它们的分工和合作来完成当前子系统的接口中的规定的所有功能和行为。
(3) 构造子系统的设计类图。其中的结点为步骤(2)中设置的各种设计元素。
(4) 构造子系统的状态图。此步骤是可选的。如果子系统具有明显的状态特征,通过UML状态图可以更清晰地描述子系统的行为特征,并且状态图有助于软件实现工程师理解子系统、实现子系统,那么可以为子系统构造状态图。如果当前子系统的状态图在分析模型中已经存在,那么在此步骤需要将此状态图进行精化,确保它与设计类图协调一致。
(5) 构造与子系统相关的活动图。此步骤也是可选的。判断是否跳过此步骤的准则与(4)基本类似,只是没有状态特征方面的要求。这里的活动图有两种作用:表示子系统内部的设计元素协同完成子系统的某些功能;表示作为一个黑箱的子系统与外部设计元素协同完成更大范围内的某些功能。如果在分析模型中与当前子系统相关的活动图已经存在,此步骤的处理方法也与(4)类似。
(6) 描述子系统与其协作者之间的依赖关系。这些协作者要么是其他子系统,要么是构件或设计类。当前子系统通过接口与这些协作者相关联,决不直接访问对方内部的设计元素。一个设计类可以直接访问子系统的服务提供接口,但子系统的服务请求接口一般仅与构件或其他子系统相连,并不直接访问子系统以外的设计类。
(7) 复核软件需求的实现程度。将有关的软件需求分解至子系统内部的设计元素或其操作,它们将成为这些设计元素的实现约束。必要时,以软件需求为目标,重新调整子系统设计模型。在此过程中,尤其要充分关注非功能性需求。必要及可能时,在软件需求与子系统内部的设计元素或设计元素集之间建立追溯关系。
(8) 设计测试用例[xxx]。基于子系统的职责和接口定义,构思子系统的典型应用场景,设计测试数据使目标软件(或其子部分)在实际运行时复现这些场景。按照软件测试的一般原则,应该尽可能完整地覆盖各种应用场景以及子系统职责的各个方面,并且采用尽可能少的测试用例来实现这种覆盖。针对子系统的测试用例将用于软件的集成测试(将子系统内部所有部件集成起来测试)和单元测试(将子系统作为单个软件单元来测试)。这些测试用例应具有如下功效:如果子系统能够顺利通过测试,就可以在一定程度上说明该子系统已经正确地履行了其职责、实现了其接口。
6.4 子系统设计活动的输出制品及出口准则
子系统设计活动的输出制品包括:子系统设计方案(交互图、设计类图、子系统与其协作者之间的依赖关系图、测试用例,以及可能的状态图、活动图),非功能性需求追踪表。
子系统设计活动的出口准则是:
(1) 子系统设计方案已可以提交软件实现。
(2) 非功能性需求对子系统中的设计元素的约束是可实现的。
6.5 子系统设计活动小结
参与子系统设计活动的角色及其职责见表11。
表11 参与子系统设计活动的角色及职责
角色 |
职责 |
子系统设计师
|
(1) 构造子系统设计模型; (2) 设计测试用例; (3) 构造“需求-设计元素”追踪表。 |
软件架构师 |
子系统设计提供必要的咨询、释疑。 |
子系统设计活动中的步骤可按次序组织为子系统设计工作流,见图45。
图45 子系统设计工作流
7 构件设计
构件设计活动的内容包括:为实现构件的职责而在其内部设置子构件和类,明确它们的职责,定义子构件和类的对外接口,确定它们之间的协作和依赖关系。由此可见,构件设计与子系统设计非常类似,请读者自行推断其参与者、进入准则、输入制品、设计方法、输出制品及出口准则,本节仅仅讨论构件设计不同于子系统设计之处:
(1) 接口与实现相分离。构件设计必须确保,实现了同一接口的两个构件可以等价替换,无论它们的内部实现方法如何不同。
(2) 为复用而设计构件。构件应该满足以下要求:
¨ 构件使用方的任何变化都不会导致构件的修改,除非构件自身提供的服务需要调整。因此,构件应具有上下文无关性,可以在不同的上下文环境中不加修改地被复用。这就意味着,构件可以与外界相互协作,但它们不能相互干扰,例如,构件不能与外界共享公共的数据结构。
¨ 在可预期的应用场景下,相同或相似的服务可以由同一构件来提供。为此,软件设计师必须分析当前及将来可能的多种应用场景中对构件的功能需求的相同点和不同点,采取以下办法:
ü 分离相同点和不同点,将相同点实现为构件。
ü 以参数化手段从不同点中抽象出公共部分,通过构件的不同配置覆盖不同点。参数化可以直接针对构件的服务功能来进行,也可以利用构件的配置机制。后者需要构件运行平台的支持,例如J2EE[6]支持以XML的方式定义构件的配置信息。
ü 将相同点抽象为框架(framework,见[11]),其中的不同点被定义为框架中的抽象服务。构件设计者只需设定抽象服务的标记(signature),并在构件实现时直接使用它们。这些抽象服务的实现体将延迟至构件使用方在复用构件时再根据实际需求来提供,见下面有关构件继承和委托机制的论述。
(3) 设计构件的定制机制。定制机制与构件的接口设计和内部实现机制息息相关。为提高构件的灵活性和可复用性,可采用以下定制机制:
¨ 最简单的定制机制就是将构件接口中定义的对外服务参数化,构件使用者通过使用不同的实在参数值来定制自己需要的构件服务。
¨ 以构件为单元定义配置信息,通过配置项的具体取值的不同来实现构件功能的定制。当构件设计者希望通过一组参数来影响构件接口定义中的多个对外服务时,此种定制机制优于最简单的针对逐个服务的参数化机制。见例18。
¨ 基于继承的定制。构件的使用者利用面向对象的继承机制为框架中的抽象服务提供适合于特定应用场景的实现体。此机制与上述基于框架的抽象方法配套使用,它需要构件运行平台对构件继承机制的支持。
¨ 基于委托的定制。此机制也必须与基于框架的抽象方法配套使用。构件在运行过程中将允许使用方定制的部分功能委托给使用方提供的抽象服务的实现体,或者说,框架型构件采用回调(callback)的方法动态整合构件使用者挂接到框架之上的抽象服务的实现体。使用此方法时,框架中的抽象服务将在构件的服务请求接口中定义,构件对外提供的服务则在服务提供接口中定义,见图49。
(4) 设计构件的组装设施。构件的组装与普通的类之间的组装有着显著的差异:类可以基于源代码进行组装,但构件通常基于执行码进行组装。为此,构件设计师必须在使用者无法获知构件源代码的条件下设计相应的组装设施,具体方法有:
¨ 基于构件描述文档的组装。在每个构件上附加一个描述性文档,使用者通过该文档来了解构件服务的使用方法(包括定制方法)和约束条件。
¨ 基于接口描述的组装。在每个构件上附加一个专门描述接口的XML文件。支持构件组装的软件开发环境读取并向构件使用方展示该文件所描述的构件接口及其使用方法。
¨ 基于自描述接口的组装。前述两种方式有两个共同的缺陷:当构件接口发生变化时,接口描述文件必须相应修改;因人为错误可能导致接口描述文件与实际的构件接口不一致。自描述接口可以圆满地解决上述问题,其基本思想是支持构件组装的软件开发环境或者构件的运行平台自动从构件的执行码中综合出构件的接口定义。在自描述接口机制的支持下,构件组装有两种途径:静态组装与动态组装。前者是指,构件使用者在开发阶段通过软件开发环境获取构件接口定义,设计构件服务请求代码;后者是指,构件使用者在开发阶段对构件接口定义一无所知,仅通过代码在程序执行时动态获取构件接口定义、动态构建对构件服务的调用代码。静态组装机制的执行效率较高,但当构件接口发生变化时使用代码必须相应修改;动态组装机制虽然可以在一定程度上避免由于构件接口的变化导致构件使用代码的变化,但其执行效率远低于静态组装,所以一般不宜采用。构件接口自描述机制的典型代表是Java的反射机制[13],JavaBeans[13]和CORBA[17]均同时支持静态组装与动态组装。限于篇幅,本书不能展开讨论这些有关构件设计的高级技术,感兴趣的读者请参阅前述文献。
8 类设计
类设计的任务是,对体系结构模型中出现的关键设计类,以及界面设计模型、子系统设计模型和构件设计模型中出现的类进行细化设计,以使它们精细至能够直接提交给软件构造阶段进行编码实现。
8.1 类设计活动的参与者
类设计由软件详细设计师负责,其他设计人员(包括软件架构师、界面设计师、用例设计师、子系统及构件设计师、数据模型设计师)在此过程中为其提供必要的咨询、释疑。
8.2 类设计活动的进入准则与输入制品
类设计活动的进入准则是:
(1) 体系结构模型、界面设计模型、用例实现方案已基本就绪。
(2) 子系统设计模型或构件设计模型已部分就绪。
(3) 职责明确。系统中类的数量往往很多,需要多名设计师参与类设计,必须事先明确他们各自的分工和职责。
根据以上准则,类的详细设计并非要等到“6 子系统设计”、“7 构件设计”所述的步骤全部完成后才启动。只要上述设计模型中某个类的职责、它与其他设计元素之间的协作关系已明确并且在后续设计时段保持相对稳定,就可以对该类展开详细设计,但必须注意使该类的详细设计及时与此后陆续成型的设计模型相一致。
类设计活动的输入制品包括:待设计的类所在的概要设计模型(体系结构模型、界面设计模型、子系统设计模型或构件设计模型),需求规格说明书。
8.3 类设计活动的步骤
类的设计工作针对每个具体的类而展开。其主要工作步骤如下:
(1) 确定类的可见范围。
(2) 精化类之间的关系。
(3) 精化类的操作和属性。
(4) 构造类的典型对象的状态图。
(5) 构造与类相关的活动图。
(6) 设计单元测试用例[xxx]。
8.3.1 确定类的可见范围
如果类仅仅被其所在的包所使用,那么该类就是“私有的”;否则就是“公开的”。确定类的可见范围应遵循以下原则:尽量缩小类的可见范围,即,除非确有必要,否则应将类“隐藏”于包的内部。
8.3.2 精化类之间的关系
本步骤的任务是在前面已获得的类图的基础上,详细研究类之间的连接关系:
(1) 根据这些连接的语义强度将它们精确地判定为UML的依赖、关联、聚合或构成关系之一;
(2) 确定连接的方向及参与连接的类的对象之间的数量对应关系;
(3) 根据软件重用的要求及软件结构简洁化、清晰化的要求优化类之间的关系。
STEP1:确定类间连接关系
类之间连接关系的语义强度从高到低依次是:继承,组合,聚合,(普通)关联,依赖。按照软件工程“强内聚、松耦合”的原则,在不违背面向对象的简单性、自然性原则的前提下,应该尽量采用语义连接强度较小的关系。此外,可以从类间连接关系在软件系统运行时发挥的作用--消息传递通道--的视角来选择最合用的连接关系。为了在对象obj1与对象obj2之间实现消息传递,面向对象的程序设计机制提供四种手段:
(1)引用全局对象。obj1直接引用作为全局对象的obj2。
(2)通过参数传递。obj2作为obj1的某项操作中的实在参数。
(3)引用局部对象。在obj1的某项操作的函数体中创建或获取obj2。
(4)通过类的成员变量。obj2作为obj1所属类的属性的取值。
前三种类型的连接具有暂时性,obj1与obj2之间的连接仅在obj1的某项操作的执行过程中建立,操作完成后连接即告终结。这种暂时性连接用UML的依赖关系表示。对最后一种具有稳定性的连接关系,需要进一步分析。如果参与连接的两个类在现实世界中存在“皮之不存,毛将焉附”型的部分—整体关系,则用UML的构成关系表示。否则,如果它们在现实世界中仍存在“多个整体对象可共享同一部件对象”的部分—整体关系,则用UML的普通聚合关系表示。如果以上两种假设均不成立,则原连接关系精化成UML中普通的关联关系。
在某些情况下,系统需要表示关联关系本身具有的属性和操作,将这些属性或操作置于参与关联的两个类之任何一个均会破坏设计模型的自然性。此时需要使用UML的关联类。关联类的精化设计方案见“2.2.3.4 关联类”。
STEP2:确定类间连接关系的方向和数量对应
UML的依赖、聚合和构成关系的方向性是非常明显的。对UML的关联关系,设计人员要仔细推敲双向关联的必要性,尽量将关联单向化,仅保留确有必要的双向关联。因为,单向关联更简单、实现代价更小。
对于UML的关联、聚合和构成关系,需进一步考虑参与关联的类的对象之间的数量对应关系以及双方对象在关联中扮演的角色,如图50所示。
STEP3:优化类间连接关系
在精化类之间的关系时,往往需要考虑到软件重用的需要而对类结构进行调整:
如果允许修改被重用的类,那么可以将被重用的类与当前设计模型中的类的共同属性和共同操作抽取至公共父类,然后适当调整两个子类的定义,见“1.4.3 面向对象的复用”。
否则,可以采用“委托”的办法,在拟重用的类和被重用的类之间建立单向关联关系。如此,拟重用的类即可通过关联关系使用被重用的类的属性和操作,见“1.4.3 面向对象的复用”。
接下来,考虑利用继承关系精化设计模型。可以从已有的类出发,寻找某些类之间的公共属性和操作,引进新的父类捕获公共性,从而简化设计模型;也可以在一定范围内按照某种准则将所有的类划分为数个集合,针对每个集合的特性设计一个父类,让集合中的所有类成为该父类的子类,这样就通过引入新父类达到了分组管理相关类的目的。此外,如果设计模型中出现了多重继承,而目标软件系统拟采用的程序设计语言不支持多重继承,那就应该将多重继承化解为单重继承,化解方法见“2.2.7.2 多重继承”。
最后,根据“强内聚、松耦合”、简单性、自然性等软件工程原则,对类之间的结构关系可以进行如下优化:
(1) 合并相互通信频繁的类。属性和操作都非常简单的类可以合并至其他类中。
(2) 分拆规模过大的类。特别地,如果一个类的属性可以区分为常用和罕用两部分,那么,为提高软件效率,可以将其分拆为一个“常用”类和数个“罕用”类,并在前者和后者之间建立聚合关系,“罕用”类的实例应按需创建。
(3) 定义嵌入类。如果类class1和类class2之间存在关联关系,但class2的对象在整个软件系统中仅被class1的对象使用,并且class2规模不大,那么可以考虑将class2嵌入class1的内部。引进嵌入类的好处是使设计模型更加简单,付出的代价是,无论是否有必要,类class2的对象都将随class1对象的实例化而存在,这样可能浪费存储空间。
8.3.3 精化类的操作和属性
对于类的每项属性,在设计模型中,可以定义属性的名称、类型、作用范围、初始值、约束条件(例如取值范围)及属性说明,后三项内容是可选的。
操作的基本内容包括名称、参数表(含参数的名称和类型)、返回类型、功能描述、前提条件(pre-condition)、出口断言(post-condition)、实现算法。前提条件是指,软件执行进入此操作前必须满足的逻辑断言;出口断言是指,在此操作执行完成的时刻点必然成立的逻辑断言。它们一般表示为逻辑表达式[12]。在设计过程中,并非对所有的操作都必须给出前提条件和出口断言,它们是可选的。
属性和操作的作用范围有以下三种:
public: 对软件系统中的所有类均可见。
protected:仅对本类及其子类可见。
private:仅对本类可见。
确定属性和操作的作用范围的基本原则是,尽量缩小作用范围,每个类仅公开那些为直接响应消息所必需的操作。原则上,属性不宜公开,如果确有必要让其他类读取或者设置该属性的值,应通过在本类中增设相应的get/set函数来实现。
为了实现类之间的关联、聚合和构成关系,可能需要在类中设置属性,方法是:如果存在从类A到类B之间的1对1关联或聚合(非构成)关系,那么可以考虑在A中设置类型为B的指针或引用(reference)的属性;如果存在从A到B之间的1对多关联或聚合(非构成)关系,那么可以考虑在A中设置一个集合类型的属性,集合元素的类型为B的指针或引用;对构成关系的处理类似于普通聚合关系,但是属性的类型(在1对多的情形下,集合元素的类型)应修改为B而非B的引用。在Java中没有指针类型,但是不可能表示非引用的类型B,所以构成和普通聚合关系的属性表示并无差异。但是,在程序逻辑中,对构成关系而言,一旦整体类的对象的生命终结,那么,属于此对象的部件类的对象必须被销毁;对普通聚合关系而言,是否将部件类的对象随同整体类的对象一并销毁则取决于业务逻辑的需要。此外,如果类A与类B之间的关联关系是双向的,或者类图要求从部件类导航(navigate)至整体类,那么还需要将前述规则沿从B到A的方向再应用一次。必须指出的是,并非所有的关联关系均须表示为属性,只要A的对象能够通过某个方法获取与其关联的B的对象,就没有必要设置专门的属性,而是在需要时使用此方法即可。
类的属性和操作还可区分为类级和实例级两种。类级的属性和操作为该类的所有实例对象所共享,它们在系统运行期间仅有单份拷贝。实例级的属性和操作则在类的每个实例对象中拥有一份独立拷贝,这些拷贝之间互不影响。
为了提高运行效率,有时需要在对象的生命同期中保存经计算生成的一些中间结果,以免重复计算,用空间换时间。此时可以引入导出属性作为类的私有属性。但是,在业务逻辑处理过程中,要特别注意导出属性值容易失效的问题。
类的操作主要来源于体系结构模型、用例实现方案、子系统设计方案、构件设计方案中对类的职责(即类的对象必须响应的消息,以及在处理这些消息的过程中必须履行的职责)的规定。这里还需要针对每个类思考添加以下操作的必要性:
(1) 对象创建:类中每个对象的初始化操作。其功能通常包括属性取值的初始化和必要的资源申请。注意,在对象创建方法中,仅申请必须伴随对象整个生命周期的资源;单项操作需要的资源应该在该操作被相应的消息激活后再按需申请,不宜在在对象创建之初就开始占用资源。
(2) 对象删除:类中每个对象在其生命周期结束前执行的最后一个操作。该操作提供了释放对象占用的资源的最后机会。为杜绝资源泄漏(即,系统不再需要曾经申请到的资源,但系统已经失去了这些资源的“句柄”,已不可能释放此资源),软件设计者和实现者必须善用此机会,确保类的所有对象均能够“清白”地退出系统。
(3) 对象比较:比较类的两个实例对象是否相同,或者比较它们的大小。
(4) 对象复制:将类的一个实例对象的属性值复制到另一对象。
如果操作的功能相对复杂,软件实现工程师不能从文字性的操作功能描述推导出软件实现代码,那么设计师还必须进行操作的算法设计。算法设计的结果可以用自然语言表达,也可以采用UML活动图来表达。算法表示的精细化程度必须符合以下标准:合格的软件实现工程师能够基于算法表示编写出符合设计师期望的程序代码。
8.3.4 构造状态图
针对类的典型对象构造状态图的方法完全可以类比于子系统的状态图的构造方法,参见“6.3 子系统设计活动的步骤”中的步骤(4)。
8.3.5 构造活动图
针对类构造活动图的方法也可以类比于子系统的活动图的构造方法,参见“6.3 子系统设计活动的步骤”中的步骤(5)。
8.3.6 设计单元测试用例
基于类的职责和接口定义,构思类的典型应用场景,设计测试数据使目标软件(或其子部分)在实际运行时复现这些场景。按照软件测试的一般原则,应该尽可能完整地覆盖各种应用场景以及类的职责的各个方面,并且采用尽可能少的测试用例来实现这种覆盖。针对类的测试用例应具有如下功效:如果一个类能够顺利通过测试,就可以在一定程度上说明该类已经正确地履行了其职责。
8.4 类设计活动的输出制品及出口准则
类设计活动对作为其输入制品的概要设计模型中的某些类进行了彻底的设计精化,因此其输出制品是精化后的概要设计模型;在完成对概要设计模型中所有类的精化后,得到可以提交软件实现的详细设计模型,包括针对每个类的单元测试用例。
类设计活动的出口准则是:对类的属性和操作的描述已可以提交软件实现。
8.5 类设计活动小结
参与类设计活动的角色及其职责见表13。
表13 参与类设计活动的角色及职责
角色 |
职责 |
详细设计师
|
(1) 精化设计模型; (2) 设计单元测试用例。 |
软件架构师 界面设计师 用例设计师 子系统及构件设计师 数据模型设计师 |
为类设计提供必要的咨询、释疑。 |
类设计活动中的步骤可按次序组织为类设计工作流,见图52。
图52 类设计工作流
9 数据模型设计
在讨论数据模型的设计方法之前,先介绍一个相关概念:持久数据操作。它包括写入、查询、更新和删除四类基本操作以及由它们复合而成的业务数据操作。写入操作将数据从运行时的软件系统保存至关系数据库;查询操作按照特定的选择准则从关系数据库提取部分数据置入运行时软件系统中的指定对象;更新操作以运行时软件系统中的(新)数据替换关系数据库中符合特定准则的(旧)数据;删除操作将符合特定准则的数据从关系数据库中删除。
数据模型设计的任务是,确定设计模型中需要持久保存的数据条目,基于关系数据模型[18]设计这些数据条目的组织方式,必要时还须设计特定于本软件项目将采用的关系数据库管理系统[18]的优化机制,以提高持久数据操作的性能。
9.1 数据模型设计活动的参与者
数据模型设计由专门的数据模型设计师负责,其他设计人员(包括软件架构师、用例设计师、子系统及构件设计师、详细设计师)在此过程中为其提供必要的咨询、释疑。
9.2 数据模型设计活动的进入准则与输入制品
数据模型设计活动的进入准则是:
(1) 需求分析模型已就绪。
(2) 职责明确。系统中需要持久保存的数据种类往往很多,需要多名设计师参与类设计,必须事先明确他们各自的分工和职责。
根据以上准则,数据模型的设计基本上要贯穿整个软件设计阶段:在体系结构设计时,应该针对关键性、全局性的数据条目建立最初的数据模型;在后续的设计过程中,数据模型应该不断丰富、演进、完善,以满足用例、子系统、构件、类等设计元素对持久数据存储的需求。
数据模型设计活动的输入制品是各个设计阶段生成的设计模型,包括:体系结构模型,用例实现方案,子系统设计方案,构件设计方案,详细设计模型等。
9.3 数据模型设计活动的步骤
数据模型设计的主要工作步骤如下:
(1) 确定设计模型中需要持久保存的类的对象及其属性。此动作必须在设计模型中相应的类已经相对稳定后才可执行。在面向对象设计模型中的类对应于关系数据模型中的“表格”(table),对象对应于“记录”(record),属性对应于表格中的“字段”(field)或者“列”(column)。哪些类的何种对象,以及这些对象的何种属性需要持久化,完全取决于业务应用对数据持久的需求。一般情况下,为节约持久存储空间,提高数据操作的效率,同时也为了避免数据库中的数据不一致性,派生属性不应进入持久存储。为了唯一地标识关系数据库表格中的一条记录,需要确定表格中的某一个或者某些字段作为“关键字”字段。关键字必须确保:表格中任意两条记录在关键字字段上的值不会全部相等。关系数据模型中的表格仍然可以采用带构造型的UML类来表示。其中<<table>>表示“表格”,<<key>>修饰关键字字段。
(2) 确定持久存储的数据之间的组织方式。在关系数据库模型中,同一类型的数据组织为表格,不同类型、但是相互关联的数据组织为表格之间通过外部关键字(外键)来搭建的关联。例如,在图53(a)中,表格T_C2中的记录通过外键key_for_C1的值可以获知相应的表格T_C1中的记录。因此,本步骤的主要任务是将面向对象的设计模型中类之间的关系映射到关系数据模型中表格之间关系:
¨ 1对1、1对多型关联关系的映射:假设类C1、C2对应的表格分别为T_C1、T_C2,只要将T_C1中关键字字段纳入T_C2中作为外键,就可表示从T_C1到T_C2之间的1对1、1对多型关联关系,见图53(a)。
¨ 多对多型关联关系的映射:在T_C1到T_C2之间引进新的交叉表格T_Intersection,将T_C1和T_C2中关键字字段均纳入T_Intersection中作为外键,在T_C1与T_Intersection之间、T_C2与T_Intersection之间建立1对多关系,见图53(b)。
¨ 继承关系的映射:假设C1是C2的父类,有两种方法在关系数据模型中表示它们之间的继承关系:
ü 将T_C1中的所有字段全部引入至T_C2,见图53(c)。这种方法的弊端是,浪费了持久存储空间,并且容易因数据冗余而导致数据不一致性。
ü 仅将T_C1中关键字字段纳入T_C2中作为外键,见图53(d)。要从关系数据模型中获取C2的对象的全部属性,则需要联合T_C2中的记录和对应于外键值的T_C1中的某条记录。这种方法避免了数据冗余,但是在读取C2的对象时性能不如前一种方法。
¨ 依赖和实现关系在数据模型中不需考虑。
(a) 将1对1、1对多型关联关系映射到关系数据模型
(b) 将多对多型关联关系映射到关系数据模型
(c) 将继承关系映射到关系数据模型的方法之一
(d) 将继承关系映射到关系数据模型的方法之二
图53 类之间的关系到数据库表格之间的关系的映射
(3) 确定数据模型中的操作行为。这里考虑的操作行为是指,仅仅与持久数据本身有关、操作逻辑非常简单、因为使用频繁而对整个目标软件系统的性能有较大影响的数据操作。需要在数据模型中考虑的比较典型的数据操作有:数据完整性验证,对数据对象或其属性的批量处理,数据求和、求均值等。数据完整性又有两种:对表格中某些字段的取值范围的约束,以及一张表格中每条记录的外键所引用的另一张表格的记录必须存在。后者又称引用完整性。
当前的关系数据库管理系统普遍支持两种形式的操作行为:存储过程[18]和触发器[18]。存储过程是命名的SQL语句[18]块,它一经定义即可供反复调用。触发器是带有执行条件的SQL语句块,触发器仅由关系数据库管理系统在触发条件成立时自动执行,不能以其他方式显式调用。
实际上,本步骤不是必需的,因为上述所有的操作行为既可采用存储过程或触发器来实现,也可以由数据持久存储服务(见“4.4.3 设计技术支撑方案”)来实现。采用第一种方做法的好处是执行效率较高,但是弊端也非常明显:存储过程和触发器破坏了面向对象的软件结构,并且,由于它们与具体的关系数据库管理系统密切相关,也破坏了目标软件系统相对于数据持久机制的独立性、可移植性。因此,本书强烈建议,一般不要使用存储过程和触发器,除非数据模型设计师经过审慎评估后确认存储过程或触发器能够显著改善系统性能,并且这样的性能提升对于目标软件系统达到预定的性能指标确有必要。
(4) 进一步优化持久数据操作的性能。在关系数据模型中,可供采用的优化方法有:
¨ 反规范化。如果需要一次性从多个表格中查询数据,关系数据库必须进行表联结(table join)。表联结操作的效率远低于单表查询,所以,在多表联结操作频繁执行时,系统性能会明显下降。此时,可考虑将多张表合并为一张表,这种手段称为“反规范化”,因为它与关系数据模型所要求的规范化设计原则[18]背道而驰。反规范化的代价是,针对未合并前的单表的查询操作效率降低,合并后数据更新的效率也会降低。所以,数据模型设计师必须权衡利弊,适度采用反规范化方法。
¨ 创建索引。索引是关系数据库管理系统提高数据查询效率的最有效的手段,它对持久数据操作的性能影响甚巨。一般而言,应该针对关键字、经常出现在查询语句和数据更新语句的条件部分的字段建立索引。索引的代价是,在写入、更新或删除数据时必须进行额外的索引更新;此外,索引也要占据一定的持久存储空间。因此,在决定对表格的哪些字段建立索引之前,数据模型设计师必须仔细分析各种数据操作的条件子句,以及它们的执行频度。如果增、删、改的执行频度高于查询的执行频度,或者前者的性能表现比后者更重要,那么被索引的字段就不宜太多;不需要针对执行频度不高,并且对性能无特殊要求的查询建立索引;如果需要大批量地进行记录的增、删、改操作,那么应该先删除索引,在大批量操作完成后再重建索引。
被索引的字段在表格的UML类表示中可以冠以构造型<<indexed>>。
限于篇幅,本书不能更详尽地讨论存储过程和触发器及其使用方法、反规范化方法、索引创建方法,有兴趣的读者请参见[18]。
9.4 数据模型设计活动的输出制品及出口准则
数据模型设计活动的输出制品是数据模型,包括以UML类图表示的数据库表格以及它们之间的关系,存储过程和触发器的定义。
数据模型设计活动的出口准则是:数据模型可以完全满足设计模型对持久数据存储的需求;数据模型可以提交给软件实现工程师进行表格创建、索引创建、存储过程和触发器编程。
9.5 数据模型设计活动小结
参与数据模型设计活动的角色及其职责见表14。
表14 参与数据模型设计活动的角色及职责
角色 |
职责 |
数据模型设计师 |
设计数据模型。 |
软件架构师 用例设计师 子系统及构件设计师 详细设计师 |
为数据模型设计提供必要的咨询、释疑。 |
数据模型设计活动中的步骤可按次序组织为数据模型设计工作流,见图55。
图55 数据模型设计工作流
10 设计整合
设计整合的目标是,整合迄今获得的各类设计模型,按照项目事先选定的设计规格说明书的模板,形成正式的设计规格说明书。设计规格说明书(详见“附录xxx 设计规格说明书模板”)的主要内容包括:
(1) 系统概述
(1.1) 文档概览:文档的结构、每部分的内容简介;文档的读者对象及阅读顺序导引。
(1.2) 术语定义:本文档中使用的所有术语、缩写、标识符的完整定义,特别包括在设计模型中出现的每个UML构造型的定义。
(1.3) 软件系统简述:宏观地描述目标软件产品的整体目标、功能,面向的用户,运行环境。这些内容主要源自需求规格说明书的“系统概述”部分(见“4.5 需求规范化”)。
(1.4) 软件设计目标:与需求规格说明书中的内容相对应,明确说明本文档中的设计模型实现了哪些功能性、非功能性需求。若有未予实现的需求,必须说明理由或延后实现的大致计划安排。
(1.5) 设计和实现约束:影响本软件产品的设计和实现的约束条件。如果没有变化,可以直接引用需求规格说明书中的相应内容,或者省略此节。
(1.6) 参考文献:本文档引用的标准、相关的本项目的其他技术文档(至少包括需求规格说明书)、参考文献。
(2) 设计指南。描述设计过程中遵循的设计原则、规则,以及采用的非常识性的软件设计经验。这部分内容不仅有助于读者理解设计模型,而且帮助软件开发机构积累设计资产。
(2.1) 用户界面设计指南。
(2.2) 体系结构设计指南。
(2.3) 用例设计指南。
(2.4) 子系统及构件设计指南。
(2.5) 类设计指南。
(2.6) 数据模型设计指南。
(3) 界面设计模型。包括屏幕的静态图示、屏幕交互图、屏幕类图,见“3 用户界面设计”。
(4) 体系结构模型。可以将体系结构模型作为单独的文档,在设计规格说明书中直接引用此文档;对于规模不大的软件系统,也可以考虑将“4 体系结构设计”所述的软件体系结构文档的内容嵌入设计规格说明书中作为其中的一部分。不论采用何种方式,文档撰写者应该通过适当的引用或内容的统一编排,避免体系结构模型与设计规格说明书(其余部分)的内容冗余或重复,并且确保二者之间的一致性。
(5) 接口设计。描述当前软件系统与外部系统之间的交互协议。
(6) 用例设计模型。
(6.1) 用例实现方案。以UML交互图的形式表示,见“5 用例设计”。说明:在用例设计步骤生成的设计类图应该整合至相应的子系统设计模型、构件设计模型或类设计模型之中,不必在此示出。
(6.2) 针对用例的测试用例。
(7) 子系统设计模型。如果在体系结构模型中未对当前软件系统划分子系统,那么可省略此部分。
(7.1) 子系统1的设计模型
(7.1.1) 设计类图。描述子系统1的结构、其中各设计元素的职责及它们之间的关系。
(7.1.2) 交互图。描述子系统1中各设计元素之间的动态协作关系。
(7.1.3) 依赖关系图。描述子系统与其他设计元素之间的依赖关系。
(7.1.4) 状态图(可选)。子系统1的状态图,见“6.3 子系统设计活动的步骤”。
(7.1.5) 活动图(可选)。与子系统1相关的活动图,见“6.3 子系统设计活动的步骤”。
(7.1.6) 测试用例。
(7.2) 子系统2的设计模型
……
(8) 构件设计模型。此部分的内容类似于子系统设计模型部分的内容。如果当前软件系统中未包含构件,那么可省略此部分。
(9) 类设计模型。逐个描述类的职责、协作者、类的属性(包括属性的名称、类型、作用范围、初始值、约束条件、属性说明等)、类的方法(包括方法的名称、功能说明、参数的名称和类型、返回类型,以及可选的前提条件、出口断言、实现算法等)、类的状态图(可选)、与类相关的活动图(可选)、针对类的单元测试用例。因为一个软件系统中类的数量往往很大,所以需要按照类所处的子系统或构件,采用结构化分层的方法来组织类设计模型,见附录xxx。
(10) 数据设计模型。以UML类图表示的数据库表格以及它们之间的关系,存储过程和触发器的定义。见“9 数据模型设计”。
(11) 需求-设计追踪表。以表格方式指明需求项与设计元素或者元素集之间的追踪关系,示例见“附录xxx 设计规格说明书示例”中的表A-xxx。此表可以作为单独的文档,在设计规格说明书中直接引用此文档。
(12) 实施指南。描述设计模型向软件实现代码映射的一般性规则,软件设计师对实现工程师如何实现本文档所述的设计模型的建议,以及软件设计师对于可能在软件构造过程中出现的问题的解决方法建议。
设计整合活动的主要参与者包括:软件设计师、文档支持工程师、质量保证工程师、配置管理工程师。其进入准则是:
(1) 软件设计活动的输出制品已就绪。
(2) 设计规格说明书的文档模板已就绪。
(3) 文档支持工程师(经过必要的培训)已具备撰写设计规格说明书所必需的技术及写作技能。
设计整合活动是在软件设计活动的工作成果的基础上展开的,其输入制品是这些活动的输出制品的并集。其主要工作步骤如下:
(1) 整合迄今获得的所有设计模型。检查并消解它们之间的不一致性,剔除冗余性,逐个用例地检查设计模型中所有元素是否能够协力完成用例目标,在软件系统全局的高度检查设计模型(在排除软件构造阶段引入的影响因素后)是否能够满足非功能性软件需求,是否能够表现出良好的软件质量属性。
(2) 整合迄今获得的所有“需求-设计追踪表”,构造统一的追踪表(见表A-xxx)。
(3) 按照设计规格说明书模板撰写设计规格说明。在此过程中可以根据项目的实际情况对模板进行适当的裁剪或扩充。
(4) 将设计规格说明书置于配置管理的控制之下。
除技术活动外,在设计整合过程中,软件质量保证工程师应当对设计规格说明书模板的采用、裁剪、扩充进行认可,同时,并行地检查、评审设计规格说明书,以便及时发现过程与输出制品中可能存在的质量缺陷。
设计整合活动的输出制品为设计规格说明书和“需求-设计追踪表”。在提交给软件构造活动之前,设计评审活动将对设计规格说明进行评审、调整。评审通过后的设计规格说明将成为软件实现和测试活动的主要依据。
设计整合活动的出口准则是:
(1) 设计规格说明书已撰写完成,并且通过了设计评审;
(2) 设计规格说明书已经置于配置管理的控制之下。
例22 设计规格说明书
见附录xxx。
参与设计整合活动的角色及其职责见表15。
表15 参与设计整合活动的角色及职责
角色 |
职责 |
软件设计师 |
(1) 整合迄今获得的所有设计模型; (2) 构造统一的“需求-设计追踪表”。 |
文档支持工程师 |
撰写设计规格说明书。 |
测试工程师 |
(1) 指导文档支持工程师撰写设计规格说明书中有关测试用例的部分; (2) 审核上述部分。 |
质量保证工程师 |
(1) 认可设计规格说明书模板; (2) 审核设计规格说明书。 |
配置管理工程师 |
对设计规格说明书进行配置管理。 |
设计整合活动可以表示为图56所示的工作流。
图56 设计整合工作流
11 设计评审
设计评审的目标是,确保设计规格说明书能够实现所有的软件需求,确保设计模型已经精化到合格的软件实现工程师能够构造出符合软件设计者期望的目标软件系统。
设计评审活动的主要参与者包括:需求工程师、用户(客户)、项目软件经理、软件实现工程师、测试工程师、质量保证工程师、配置管理工程师和软件设计师。其进入准则是:待评审的设计规格说明已经置于配置管理之下。
设计评审活动的输入制品是来自配置库的设计规格说明书。
设计评审的主要步骤与“4.6 需求验证”中所述的需求验证步骤大体相似,参见图4-31。设计评审与需求验证的主要不同点在于设计评审的主要关注点:
(1) 设计模型是否能够充分地、无遗漏地支持所有软件需求的实现;
(2) 设计模型是否已经精化至合理的程度,可以确保合格的软件实现工程师能够构造出符合软件设计者期望的目标软件系统;
(3) 设计模型的质量属性,即,设计模型是否已经经过充分的优化,以确保依照设计模型构造出来的目标软件产品(在排除软件构造阶段引入的影响因素后)能够表现出良好的软件质量属性,尤其是正确性、有效性、可靠性和可修改性。
设计评审活动的输出制品是通过评审的设计规格说明书。它是整个软件设计阶段的最终输出,将成为软件实现和测试活动的主要依据。
设计评审活动的出口准则是:设计规格说明书已通过评审,评审过程中发现的所有问题均已解决,并且设计基线已经建立。
参与设计评审活动的角色及其职责见表16。
表16 参与设计评审活动的角色及职责
角色 |
职责 |
需求工程师 |
评审设计规格说明书,重点关注设计模型实现软件需求的程度。 |
用户(客户) |
评审界面设计模型。 |
项目软件经理 |
(1) 主持设计规格说明书的评审; (2) 必要时,针对评审中发现的部分问题,管理其解决方案的实施。 |
软件实现工程师 |
评审设计规格说明书,重点关注设计模型的可理解性、一致性、精确性、可行性。 |
测试工程师 |
评审设计规格说明书,重点关注设计模型的可测试性和其中的测试用例。 |
质量保证工程师 |
(1) 审查设计评审申请; (2) 评审设计规格说明书; (3) 跟踪评审中发现的所有问题直至解决。 |
配置管理工程师 |
对设计规格说明书进行配置管理,必要时建立设计基线。 |
软件设计师 |
(1) 参与评审设计规格说明书,在评审过程中进行必要的解释或说明; (2) 解决评审中发现的所有问题。 |
小结
软件设计是需求工程与软件构造两个阶段之间桥梁,其目标是获取能够满足软件需求的、明确的、可行的、高质量的软件解决方案。一个完整的设计过程通常包括策划、界面设计、体系结构设计、用例设计、子系统设计、构件设计、类设计、数据模型设计、设计整合、设计评审、设计管理等活动。对中、大型软件项目,设计过程往往采用迭代方式,经过反复求精后才能获得高质量的软件设计模型。
设计策划活动的主要目的是就单次迭代的任务、工作分工、进度计划在所有参与者之间达成共识,为此必须进行风险分析并制定必要的应对措施,必须明确定义迭代的目标及验收准则,必须制订本次迭代的工作计划。
界面设计是提高软件易用性的关键环节,必须高度重视。其目标是为用户提供友好的人机交互界面。为此,界面设计师必须掌握基本的人机工程学和美工设计等方面的知识。界面设计的关键步骤包括:针对每个用例设计主屏幕、基于主屏幕设计设计屏幕跳转流和从屏幕,在整个软件系统范围内整合多个用例的屏幕和屏幕流,构造界面原型并评审之。
体系结构设计的目标是建立软件系统的体系结构,它明确地定义软件各子系统、关键构件、关键类的职责划分及协作关系,并且针对软件系统中全局性、基础性的技术问题给出技术基础设施或解决方案。体系结构设计师在开始设计之前应该了解相关的体系结构模式,掌握其使用方法。体系结构设计的关键步骤包括:开发初始的软件顶层架构,搜索并选取可用的设计资产,研究公共的基础性软件技术问题并设计技术支撑方案,确定设计元素,开发软件部署模型,设计并发机制,构建软件体系结构模型并评审之。
用例设计的目标是确保界面设计模型、体系结构模型与需求工程简单获得的用例模型的符合性。在软件设计过程中引入用例设计环节,是为了在子系统设计、构件设计、类设计、数据模型设计尚未全面铺开之前,以软件需求为准则来检验界面设计模型和体系结构模型的符合性,及早发现不一致性并立即改正。用例设计的关键步骤包括:针对每个用例给出以UML交互图表示的软件实现方案,据此构造设计类图并设计测试用例;从全局和整体的高度整合所有的用例实现方案。
子系统设计的目标是确定子系统内部的结构。其关键步骤包括:将子系统的服务提供接口中规定的职责分配给子系统中的软件设计元素,构造子系统的设计类图,必要时构造子系统的状态图和相关的状态图,描述子系统与其协作者之间的依赖关系,设计子系统的测试用例。
构件设计与子系统设计非常类似,但是构件设计非常强调接口与实现相分离、为复用而设计。此外,构件设计师还必须精精心设计构件的定制和组装机制。
类设计的任务是,对体系结构模型中出现的关键设计类,以及界面设计模型、子系统设计模型和构件设计模型中出现的类进行细化设计,以使它们精细至能够直接提交给软件构造阶段进行编码实现。其关键步骤包括:精化类之间的关系,精化类的操作和属性,必要时构造类的典型对象的状态图和与类相关的活动图,设计单元测试用例。
数据模型设计的任务是,确定设计模型中需要持久保存的数据条目,基于关系数据模型设计其组织方式,必要时还须设计特定于关系数据库管理系统的优化机制,以提高持久数据操作的性能。其关键步骤包括:确定设计模型中需要持久保存的类的对象及其属性,确定数据组织方式,确定数据模型中的操作行为,通过合适的索引、反规范化等手段优化持久数据操作的性能。
设计整合的目标是形成正式的设计规格说明书。设计规格说明必须遵循规范的格式,能够直接成为了解构造的基础。设计整合的主要工作步骤包括:整合迄今获得的所有设计模型及“需求-设计追踪表”,按照设计规格说明书模板撰写设计规格说明,将设计规格说明书置于配置管理的控制之下。
设计评审的目标是,确保设计规格说明书能够实现所有的软件需求,确保设计模型已经精化到合格的软件实现工程师能够构造出符合软件设计者期望的目标软件系统。通过评审后的设计规格说明书应该置于基线管理控制之下。
软件设计是迈向最终软件解的漫长征途中的非常关键的一程。现在,终于可以开始基于设计模型进行软件构造,可运行的软件系统已经变得触手可及了。