软件概述
基本定义
(1) 程序与软件的定义
程序是由程序设计语言所描述的、能为计算机所理解和处理的一组语句序列。其用程序设计语言(Programming Language)来描述的。程序严格遵循程序设计语言的各项语法和语义规定,应确保程序代码能为程序设计语言的编译器所理解,进而编译生成相应的可运行代码。
软件是指在计算机系统的支持下,能够完成特定功能与性能的程序、数据和相关文档。文档是记录软件开发活动和阶段性成果、软件配置及变更的阐述性资料。
(2) 软件的性质
- 复杂性
- 一致性:软件不能独立存在,需要依附于一定的环境(硬件网络等),软件必须遵从人为的惯例并适应已有的技术和系统,软件需要随接口不同而改变,随时间推移而变化,而这些变化是不同人设计的结果
- 可变性:人们总认为软件是容易修改的,但忽视了修改带来的副作用,不断的修改最终导致软件的退化,从而结束其生命周期
- 不可见性:软件是一种看不见、摸不着的逻辑实体,不具有空间的形体特征,开发人员可以直接看到程序代码,但源代码并不是软件本身,软件以机器代码的形式运行,开发人员无法看到源代码如何执行的。
(3) 软件的分类
类别 | 服务对象 | 软件的功能 | 发挥的作用 |
---|---|---|---|
应用软件 | 行业和领域应用的用户 | 为特定行业和领域问题解决提供基于软件解决方案,创新应用领域的问题解决模式 | 提供更为便捷、快速、高效的服务 |
系统软件 | 各类应用软件 | 为应用软件运行和维护提供基础设施和服务,如加载、通讯、互操作、管理等 | 作为应用软件的运行环境 |
支撑软件 | 软件开发者和维护者 | 为软件系统的开发和维护提供自动和半自动的支持 | 提高软件开发效率和质量 |
(4) 开源许可证
软件工程概述
(1) 软件开发需要解决的问题
- 开发过程:基于什么样的步骤来开发软件系统
- 开发方法:采用怎样的方法来指导各项软件开发活动
- 开发管理:如何组织开发人员和管理软件产品
- 质量保证:如何保证软件开发活动和制品的质量
(2) 软件开发的特殊性
软件开发技术更新速度快、软件开发需求变更频繁、软件开发复杂度高、软件开发需要团队协作完成、软件开发成果需要长期维护、软件开发保证软件质量、软件开发需要评估和控制风险
(3) 软件工程的定义
- 软件工程是一门研究如何用系统化、规范化、可量化等工程原则和方法进行软件开发和维护的学科。系统化:提供完整和全面的解决方法,包括目标、原则、过程模型、开发活动、开发方法和技术等;规范化:支持各类软件系统的开发,包括语言标准、质量标准、编程标准、方法标准、能力极其改进标准等;可量化:工作量、成本、进度、质量等要素可以量化。
- 其目标是创造“足够好”的软件,对于好的软件的定义:低成本、高质量、按时交付
- 其内容包括市场调研、正式立项、需求分析、项目策划、概要设计、详细设计、编程、测试、试运行、产品发布、用户培训、产品复制、销售、实施、系统维护和版本升级等。
(4) 软件工程的三要素
视角 | 描述 | 目的 |
---|---|---|
过程 | 从管理的视角,回答软件开发、运行和维护需要开展哪些工作、按照什么样的步骤和次序来开展工作 | 对软件开发过程所涉及的人、制品、质量、成本、计划等进行有效和可量化的管理 |
方法学 | 从技术的视角,回答软件开发、运行和维护如何做的问题 | 为软件开发过程中的各项开发和维护活动提供系统性、规范性的技术支持 |
工具 | 从工具辅助的视角,主要回答如何借助工具来辅助软件开发、运行和维护的问题 | 帮助软件开发人员更为高效地运用软件开发方法学来完成软件开发过程中的各项工作,提高软件开发效率和质量,加快软件交付进度 |
计算机辅助软件工程
(1) 基本概念
全称为Computer Aided Software Engineering(CASE)。起源于20世纪80年代,最初是指在信息管理系统开发过程中由各种计算机辅助软件和工具组成的软件开发环境。随着软件工程技术、工具和开发理念的不断发展,CASE逐步演进成为辅助软件工程全生命周期的开发工具和方法集合。CASE旨在帮助软件工程从业者们进行软件开发和维护,提高软件开发和运维效率,提升软件质量,为实现软件开发全生命周期的工程化、自动化和智能化提供基础支撑。
(2) CASE工具分类
工具分类 | 功能 | 利益相关方 |
---|---|---|
系统分析与设计工具 | 可视化建模,需求分析 | 需求分析人员,软件设计人员 |
程序设计工具 | 代码编写,代码补全,程序调试 | 编程人员,维护人员 |
软件测试与质量保证工具 | 单元测试,集成测试,性能测试 | 编程人员,测试人员 |
项目管理工具 | 代码和文件托管,团队协作 | 开发人员,管理人员 |
软件运维工具 | 应用程序部署,管理,监控 | 开发人员,维护人员 |
一体化集成开发环境 | 全生命周期管理 | 软件开发团队全体成员 |
过程模型
传统软件过程
需求开发:可行性研究之后,分析、整理和提炼所收集到的用户需求,建立完成的需求分析模型,编写软件需求规格说明
软件设计:根据需求规格说明,确定软件体系结构,进一步设计每个系统部件的实现算法、数据结构及其接口等。
软件构造:概括地说是将软件设计转换成程序代码,这是一个复杂而迭代的过程,要求根据设计模型进行程序以及正确而高效地编写和测试代码
软件测试:检查和验证所开发的系统是否符合用户期望,主要包括单元测试、子系统测试、集成测试和验收测试等活动
软件维护:系统投入使用后对其进行改进,以适应不断变化的需求。完全从头开发的系统很少,将软件系统的开发和维护堪称一个连续过程更有意义。
软件项目管理:为了使软件项目能够按照预定的成本、进度、质量顺利完成,而对成本、人员、进度、质量和风险进行控制和管理的活动。
软件配置管理:通过执行版本控制、变更控制的规程,并且使用合适的配置管理软件,保证所有产品配置项的完整性和可跟踪性。
(1) 瀑布模型
步骤 | 活动 | 方法 | 产出 |
---|---|---|---|
需求分析(Requirement Analysis) | 任务:定义软件需求,包括功能、非功能需求 关注点:要做什么?(What, Problem) 层次和视角:用户角度,仅描述问题和需求 | 依据:用户的期望和要求 不断与用户进行交流和商讨,抽象、问题分解、多视点等技术 | 软件需求模型、软件需求文档、软件确认测试计划 文档类的软件制品 |
概要设计(Architecture Design) | 任务:建立软件总体架构、制定集成测试计划 关注点:软件高层设计? (How, Solution) 层次和视角:宏观、全局、整体、战略性 | 依据:软件需求文档 自顶向下, 逐步求精, 抽象, 模块化, 局部化,信息隐藏 …… | 软件概要设计模型、软件概要设计文档、软件集成测试计划 文档类的软件制品 |
详细设计(Detailed Design) | 任务:设计模块内部细节(算法、数据结构),制订单元测试计划 关注点:详细设计? (How,Solution) 层次和视角:微观、局部、细节性 | 依据:概要设计文档、软件需求文档 高质量的软件设计原则,如单入口单出口 | 软件详细设计模型、软件详细设计文档、单元测试计划 文档类的软件制品 |
编程实现(Implementation) | 任务:编写程序代码并进行单元测试和调试 关注点:如何最终做出这个东⻄? (How,Code) 层次和视角:最终的实现代码 | 依据:软件概要和详细设计文档、单元测试计划 采用某种程序设计语言(如C、C++、Java) | 经过单元测试的源程序代码 程序类的软件制品 |
集成测试(Integration Test) | 任务:组装软件模块并进行测试以发现问题 关注点:集成后软件中的缺陷(Bug) 层次和视角:自底向上组装、全局 | 依据:软件概要设计文档、软件集成测试计划 软件集成测试工具 | 经过集成测试、修复缺陷的源程序代码,集成测试报告 数据、文档和代码类的软件制品 |
确认测试(Validation Test) | 任务:测试软件是否满足用户需求 关注点:软件在满足用户需求方面是否存在缺陷 层次和视角:从用户角度,聚焦需求是否得以正确实现 | 依据:软件确认测试计划、软件需求文档 软件测试支撑工具 | 经过确认测试、修复缺陷后的代码,软件确认测试报告 数据、文档和代码类的软件制品 |
软件需求具有易变、多变的特点,而瀑布模型需求确定,过于理想化,缺乏变通,难应对变化。将其改进,可以得到带反馈和回溯的瀑布模型:
(2) 增量模型
(3) 迭代模型
增亮模型和迭代模型的区别
(4) 原型模型
(5) 螺旋模型
(6) 不同模型对比
模型名称 | 指导思想 | 关注点 | 适合软件 | 管理难度 |
---|---|---|---|---|
瀑布模型 | 提供系统性指导 | 与软件生命周期相一致 | 需求变动不大、较为明确、可预先定义的应用 | 易 |
原型模型 | 以原型为媒介指导用角户的需求导出和评价 | 需求获取、导出和确角认 | 理解需求难以表述清楚、角不易导出和获取的应用 | 易 |
增量模型 | 快速交付和并行开发 | 软件详细设计、编码角和测试的增量式完成 | 需求变动不大、较为明确、角可预先定义的应用 | 易 |
迭代模型 | 多次迭代,每次仅针角对部分明确软件需求 | 分多次迭代来开发软角件,每次仅关注部分角需求 | 需求变动大、难以一次性角说清楚的应用 | 中等 |
螺旋模型 | 集成迭代模型和原型角模型,引入风险分析 | 软件计划制定和实施,软件风险管理,基于角原型的迭代式开发 | 开发风险大,需求难以确角定的应用 | 难 |
敏捷方法
(1) 传统软件过程模型的特点和不足
软件开发和运维的大量工作用于撰写软件文档,而非去编写程序代码
软件开发过程中会花费大量时间和精力用于软件文档的评审,以确保软件质量
一旦软件需求发生变化,开发人员需要修改软件需求文档,并据此来调整其他的一系列文档,最后再修改程序代码
等较长时间才能得到可运行软件系统
软件开发和运维的大量工作用于撰写软件文档,而非去编写程序代码
软件开发过程中会花费大量时间和精力用于软件文档的评审,以确保软件质量
一旦软件需求发生变化,开发人员需要修改软件需求文档,并据此来调整其他的一系列文档,最后再修改程序代码
等较长时间才能得到可运行软件系统
(2) 概念
是一种轻量级软件开发方法,主张软件开发要以代码为中心,快速、轻巧和主动应对需求变化,持续、及时交付可运行的软件系统,提供了一组思想和策略,指导快速响应用户需求的变化,快速交付可运行的软件制品
(3) 敏捷准则
- 尽早和持续地交付有价值的软件,以使用户满意
- 即使到了软件开发后期,也欢迎用户需求的变化
- 不断交付可运行的软件系统,交付周期可以从几周到几个月
- 在整个软件项目开发期间,用户和开发人员最好能每天一起工作
- 由积极主动的人来承担项目开发,给他们提供所需环境和支持,信任他们的能力
- 团队内部最有效的信息传递方式是面对面的交谈
- 将可运行软件作为衡量软件开发进度的首要标准
- 可持续性的开发,出资方、开发方和用户方应当保持⻓期、恒定的开发速度
- 关注优秀的技能和良好的设计会增强敏捷性
- 简单化
- 最好的架构、需求和设计出自于自组织的团队
- 软件开发团队应定期就如何提高工作效率的问题进行反思,并进行相应的调整
(4) 突出代表
极限编程
(1) 四条核心思想
- 交流,强调基于口头(而非文档、报表和计划)的交流
- 反馈,通过持续、明确反馈来获得软件状态
- 简单,用最简单的技术来解决问题
- 勇气,快速开发并在必要时具有重新进行开发的信心
(2) 五条指导原则
- 快速反馈: 从用户处迅速得到有关软件的反馈,确认开发是否满足用户需求,通过自动化测试迅速了解软件运行状况
- 简单性假设: 开发人员只考虑当前迭代所面临问题,无需考虑下一次迭代的问题,用简单方法和技术来解决问题。
- 逐步更改: 通过一系列修改来逐步解决问题和完善系统,不要期望一次迭代就开发出完整的软件系统。
- 支持变化: 欢迎用户改变需求,支持用户需求动态变化。
- 高量的工作: 采用诸如测试驱动开发等技术高质量地开展工作,确保软件质量。
(3) 十二条核心准则
- 计划游戏: 软件开发团队快速制定下一次迭代的软件开发计划
- 隐喻: 使用业务相关术语来描述需求,促使开发人员和业务人员对系统达成共同和一致的理解
- 小型发布: 经常性发布可运行软件系统,每次发布的软件系统仅提供少量功能
- 简单设计: 只为当前的需求做设计,程序能运行所有测试、没有重复逻辑、包含尽可能少的类和方法
- 测试: 测试应在编写代码之前进行
- 重构: 在不改变程序代码功能的前提下,改进程序代码的设计,使程序代码更加简单,更易于扩展
- 结对编程: 两名程序员同时在一台计算机上共同开展编程工作
- 代码集体拥有: 开发小组的任何成员都可以查看并修改任何部分的代码
- 持续集成: 经常性地进行集成
- 每周工作40小时: 倡导质量优先
- 现场用户: 用户代表在现场办公,参与开发全过程,确保能及时得到反馈
- 编码标准: 遵循统一编码标准,以提高软件系统的可理解性和可维护性
Scrum方法
(1) 大致流程
- 首先,产品拥有者需创建软件产品订单库即“Backblog”。描述软件产品需提供的功能需求以及它们的优先级排序
- 其次,筛选出最应该实现的软件需求。Scrum主人基于“Backblog”中各项软件需求及其优先级,形成待实现的软件产品冲刺订单库,即“SprintLog”
- 然后,软件开发将进入冲刺“Sprint”周期。以实现选定软件订单,每个冲刺就是一次增量开发,一般持续1到4周
- 最后,共同开展Scrum评审。一次冲刺完成后,每个团队成员演示自己的开发成果,大家共同审查成果是否高质量地实现了既定功能,并就其中的问题进行反思,以指导和改进下一次冲刺
(2) 迭代开发的要点
- 每一次迭代都建立在稳定的质量基础上,并做为下一轮迭代的基线,整个系统的功能随着迭代稳定地增长和不断完善。
- 每次迭代要邀请用户代表验收,提供需求是否满足的反馈。
- 在一次迭代中,一旦团队作出承诺,就不允许变更交付件和交付日期;如果发生重大变化,产品负责人可以中止当次选代。
- 在迭代中可能会出现“分解”和“澄清”,但是不允许添加新工作或者对现有的工作进行“实质变更”。
- 对于“分解”和“澄清”,如果存在争议,那么将其认定为变更,放到产品订单中下一次迭代再考虑。
群体化软件开发方法
(1) 概念
群体话软件开发方法一方面基于结对和团队的软件项目组织和开发方法,即依靠结对和团队的力量和不同人员间的交流与协作来进行软件开发、任务分工、达成共识、成果分享等,是当前软件开发的一种常见方法。另一方面源自开源软件开发的一种全新软件开发方法-群体化开发,具有新的理念、思想,采用新的技术和手段。
此方法是一种依托互联网平台来吸引、汇聚、组织和管理互联网上的大规模软件开发人员,通过竞争、合作、协商等多种自主协同方式,让他们参与软件开发、分享软件开发知识和成果、贡献智慧和力量的一种新颖软件开发方法。
(2) 与团队软件开发方法对比
基于团队软件开发方法 | 基于社区群体化开发方法 | |
---|---|---|
项目组织方式 | 开发团队边界封闭 | 软件开发边界开放 |
开发成员组成 | 域外人员无法参与 | 互联网大众自由参与 |
程序代码开放 | 人员确定资源有限 | 利用海量的大众资源 |
依托平台协同 | 项目的成果不共享 | 共享源程序代码 |
关注创作和生产 | 集中化的管理模式 | 兼顾软件创作和生产 |
可用资源情况 | 关注生产而非创作 | 依托互联网平台 |
软件需求基础
基本概念
(1) 定义
- 从软件本⾝的角度,软件需求是指软件用于解决现实世界问题时所表现出的功能和性能等方面的要求
- 从软件利益相关方的角度,软件需求是指软件系统的利益相关方对软件系统的功能和质量,以及软件运行环境、交付进度等方面提出的期望和要求
- 软件需求刻画了软件系统能做什么(What to do),应表现出怎样的行为,需满足哪些方面的条件和约束等要求
(2) 组成部分
- 软件功能性需求(Functional):能够完成的功能及在某些场景下可展现的外部可见行为或效果
- 软件质量方面的需求(Quality):包含外部质量属性,如外部可展现的,用户、客户等会非常关心,如运行性能、可靠性、易用性等;内部质量属性,隐藏在内部的,软件开发工程师会非常关心,如可扩展性、可维护性、可理解性
- 软件开发约束性需求(Constraint):开发成本、交付进度、技术选型、遵循标准等方面提出的要求
(3) 特点
隐式性:来自于利益相关方,它隐式存在,很难辨别,甚至会遗漏掉
隐晦性:在利益相关方的潜意识之中,不易于表达出来,难以获取,所表达的软件需求存在模糊性、歧义性、二义性
多源性:存在多个的利益相关方,且可能存在相冲突和不一致的软件需求
易变性:用户对软件的期望和要求也会经常性地发生变化,甚至在整个生命周期都会发生变化
领域知识的相关性:软件需求的内涵与软件所在领域的知识息息相关,例如“12306”与铁路旅客服务领域相关
价值不均性:不同的软件需求对于客户或用户而言所体现的价值是不一样的,可以细分为:主要和次要、核心和外围需求
(4) 质量要求
- 有价值(Valuable):基于计算机软件的解决方案,有效提高问题解决的效率和质量,促进相关领域的业务创新
- 正确(Right):反映利益相关方的期望,不能曲解或误解他们的要求
- 完整(Complete):不能有遗漏或丢失
- 无二义(Unambiguous):软件需求的描述应该是清晰和准确的
- 可行(Feasible):在技术、经济等方面应该是可行的
- 一致(Consistent):不应存在冲突
- 可追踪(Traceable):可追踪到其源头
- 可验证(Verifiable):可找到某种方式来检验软件需求是否在软件系统中得到实现
UML的多视点建模
视点名称 | 描述 | UML图示及用途 |
---|---|---|
结构视点(Structural View) | 用于描述系统的构成 | 包图(Package Diagram):展示系统的分组结构,如子系统或模块。 类图(Class Diagram):表示系统中的类、接口及其关系,是系统静态结构的核心表达。 对象图(Object Diagram):显示特定时间点的对象和它们之间的连接,是对类图的具体实例化。 构件图(Component Diagram):描绘了系统的组成部分以及这些部分之间的依赖关系,反映了系统的物理结构。 |
行为视点(Behavioral View) | 刻画系统的行为 | 交互图(Interaction Diagram):包括序列图(Sequence Diagram)和通信图(Communication Diagram),用以展示对象间的交互过程。 状态图(Statechart Diagram):描绘一个实体基于事件反应的动态行为,通过状态和转移来体现。 活动图(Activity Diagram):展示了系统的流程,强调的是操作的顺序和条件分支,可以用来表现并发处理。 |
部署视点(Deployment View) | 刻画目标软件系统的软件制品及其运行环境 | 部署图(Deployment Diagram):描述了硬件节点及其上的软件组件部署情况,反映了系统的物理拓扑结构和分布特性。 |
用例视点(Use Case View) | 刻画系统的功能 | 用例图(Use Case Diagram):用于捕捉系统的功能需求,即系统应该做什么,它描述了用户(执行者)与系统之间的交互作用。 |
导出和构思软件需求
(1) 识别利益相关方
软件需求来自于软件的利益相关方,要获取软件需求,首先要搞清楚软件系统有哪些利益相关方。软件系统的利益相关方可以表现为特定的人群和组织,也可以是一类系统。不仅软件用户或客户可以是软件的利益相关方,软件的开发者也可以成为软件的利益相关方。
(2) 导出和构思软件的功能性需求
一方面需求工程师可以通过与利益相关方的交互,听取他们对软件的期望和要求,从他们那里导出软件需求,可以采用多种方法来导出软件需求:包括与用户或客户的面谈、分析业务资料、观察业务流程、进行问卷调查、软件原型等。另一方面需求工程师需要充当软件利益相关方的角色,站在他们的视角,来构思软件需求。针对软件需求开展创作,结合软件解决方案,提出可有效促进问题解决的软件需求。需求工程师可以采用大脑风暴构思、群体化方法、问卷调查、软件原型等多种方式来开展需求构思工作。
(3) 导出和构思软件的非功能性需求
非功能性需求包括软件质量要求和软件开发的约束性要求。质量要求又可以分为外部和内部。软件运行性能、可靠性、易用性、安全性、私密性等属于外部质量要求,软件可扩展性、可维护性、可互操作性、可移植性等属于内部质量要求。约束性要求,包括了开发进度要求、成本要求、技术选型等要求。软件的非功能性需求变得越来越重要,在某些情况下它们直接决定了软件是否能用和可用、是否好用和易用、是否高效和可靠运行、是否便于维护和演化等。
用例图
(1) 概念
用例图描述软件系统的边界以及软件外部使用者所观察到的系统功能,“观察到”是指外部使用者与系统存在交互,即信息输入和输出。用例图由三部分构成:执行者、用例、边。
(2) 执行者
执行者是系统之外的实体,他们使用软件系统功能、与软件系统交换信息,可以是一类用户,也可以是其他软件系统或物理设备。具体提来说,是UML中的类,代表一类用户或者外部实体,而非具体的对象实例。执行者通常对应于软件系统的利益相关方。
(3) 用例
用例是执行者为达成一项相对独立、完整的业务目标而要求软件系统完成的功能。通常表现为执行者与系统之间的业务交互动作的序列。对于执行者而言,交互目的或者效果在于达成其业务目标。对于待开发系统而言,交互的过程即是某项相对独立、完整的外部可见功能的实现过程。
(4) 执行者与用例间的关系
执行者与用例之间存在交互:执行者触发用例执行,向用例提供信息或从用例获取信息。触发用例执行的执行者称为主动执行者,仅从用例获取信息的执行者称为被动执行者。在用例图中,执行者与用例间的边通常为无向边。
(5) 用例间的关系
1. 包含关系
如果用例B是用例A的某项子功能,则称用例A包含用例B
- 包含关系用于提取多个用例中的公共子功能,以避免重复和冗余
- 体现了功能分解和组织的思想
2. 扩展关系
如果用例A与B相似,但A的功能较B多,A的动作序列是在B的动作序列中的某些执行点上插入附加动作序列而构成的,则称用例A扩展用例B
==包含和扩展的关系==
扩展关系(Extend):当某个新用例在原来的用例基础上增加了新的步骤序列,则原来用例被称为基用例,这种关系称为扩展关系,可以这样理解这里的基用例是一个完整的用例,即使没有子用例的参与,也可以完成一个完整的功能,只有当扩展点被激活时,子用例才会被执行。由子用例指向基用例,比如说充值金额查询用例中有导出Excel子用例,离开子用例不影响充值金额查询的功能,这就是扩展关系。
包含关系(include):几个用例可以提取他们共用的用例作为子用例,使其成为自己行为的一部分,因为子用例被提出,基用例并非一个完整的用例,所以include关系中的基用例必须和子用例一起使用才够完整,子用例也必然被执行。由基用例指向子用例,比如几个用例都要用到登录子用例,登录作为子用例没有它的参与,其他用例也无法执行,这就是包含关系。
比较:容易混淆的原因在于不理解扩展和包含的含义,所谓扩展是从基用例的基础上扩展出新的功能(子用例),子用例不影响基用例,基用例本身是完整的,没有子用例的参与也可以完成自己的功能,而包含关系是提取出来的用例是基用例的一部分基用例和子用例必须一起使用才完整。二者的关键在于离开子用例,基用例是否可以完成一个完整的功能。
原文链接:https://blog.csdn.net/kdongyi/article/details/89924780
3. 继承关系
如果A与B相似,但A的动作序列是通过改写B的部分动作或者扩展B的动作而获得的,则称用例A继承用例B
4. 边界框
表示整个软件系统或子系统的边界
- 边界框内的用例构成了系统或子系统的内容,如用例
- 外面的是系统之外的执行者
5. 详细描述
- 用例名:用户登录
- 用例标识: UC-UserLogin
- 主要执行者:家属、医生
- 目标:通过合法身份登录系统以获得操作权限
- 范围:空巢老人看护软件
- 前置条件:使用App软件之时
- 交互动作:
- 用户输入账号和密码
- 系统验证用户账号和密码的正确性和合法性
- 验证正确和合法则意味着登录成功
软件需求分析
基本概念
(1) 任务
基于初步软件需求,进一步精化和分析软件需求,确定软件需求优先级,建立软件需求模型,发现和解决软件需求缺陷,形成高质量的软件需求模型和软件需求规格说明书
(2) 不同视角表示
视点名称 | 描述 | UML图示及用途 |
---|---|---|
用例视点(Use Case View) | 具有哪些功能、功能间有何关系、功能与利益相关方有何关系 | 用例图(Use Case Diagram):分析和描述用例视角的软件需求模型,捕捉系统的功能需求,描述用户(执行者)与系统之间的交互作用。 |
行为视点(Behavioral View) | 用例是如何通过业务领域中一组对象以及它们间的交互来达成的 | 交互图(Interaction Diagram)、状态图(Statechart Diagram):描述行为视角的软件需求模型,展示对象间的交互过程和实体基于事件反应的动态行为。 |
结构视点(Structural View) | 业务领域有哪些重要的领域概念以及它们之间具有什么样的关系 | 类图(Class Diagram):描述和分析业务领域的概念模型,表示系统中的类、接口及其关系,是系统静态结构的核心表达。 |
相关的UML图
交互图
用于刻画对象间的消息传递,分析如何通过交互协作完成功能,主要可以表示用例的功能实现方式、软件系统在某种使用场景下对象间的交互协作流程、软件系统的某个复杂操作的逻辑实现模型。交互图可以分为和顺序图(Sequence Diagram)和通信图(Communication Diagram),前者强调消息传递的时间序,后者突出对象间的合作。两种交互图表达能力相同,可相互转换。
顺序图
(1) 形状
描述对象间的消息交互序列
- 纵向:时间轴,对象及其生命线(虚线),活跃期(⻓条矩形)
- 横向:对象间的消息传递
(2) 表示方式
- 对象
- “[对象名] : [类名]
- 示例:“Tom:Student”或“Student”
- 消息传递
- 对象生命线间的有向边
- “[*][监护条件] [返回值:=]消息名[(参数表)]”
- “*”为迭代标记表示同一消息对同一类的多个对象发送
(3) 对象间的消息传递
- 同步消息:发送者等待接收者将消息处理完后再继续
- 异步消息:发送者在发送完消息后不等待接收方即继续自己的处理
- 自消息:一个对象发送给自身的消息
- 返回消息:某条消息处理已经完成,处理结果沿返回消息传回
- 创建消息和销毁消息:消息传递目标对象的创建和删除
(4) 示例
通信图
主要特点:节点表示对象、对象间连接称为连接器、连接器上可标示一到多条消息、消息传递方向用靠近消息小箭头表示、消息序号采用多层标号。
视点 | 图 (diagram) | 说明 |
---|---|---|
结构 | 包图(package diagram) | 从包层面描述系统的静态结构 |
类图(class diagram) | 从类层面描述系统的静态结构 | |
对象图(object diagram) | 从对象层面描述系统的静态结构 | |
构件图(component diagram) | 描述系统中构件及其依赖关系 | |
行为 | 状态图(statechart diagram ) | 描述状态的变迁 |
活动图(activity diagram) | 描述系统活动的实施 | |
通信图(communication diagram) | 描述对象间的消息传递与协作 | |
顺序图(sequence diagram) | 描述对象间的消息传递与协作 | |
部署 | 部署图(deployment diagram) | 描述系统中工件在物理运行环境中的部署情况 |
用例 | 用例图(use case diagram) | 从外部用户角度描述系统功能 |
类图
(1) 图的构成
结点:表示系统中的类(或接口)及其属性和操作
边:类之间的关系
(2) UML表示
(3) 属性的表示
- [可见性] 名称 [: 类型] [多重性] [= 初值] [{约束特性}]
- 可见性
- 公开(+): 所有对象均可访问
- 保护(#): 所在类及子类对象均可访问
- 私有(-): 仅所在类的对象才可访问
- 多重性:属性取值数量, 如1,0..1,0..* ,1..,
- 约束特性
- 可更改性:{readOnly}表示只读,缺省为{changeable}
- 顺序性: {ordered}表示属性取值是有序的,缺省为{unordered}
- 唯一性: 缺省为{bag}表示属性取值元素允许出现重复元素
- 静态性:{static}表示静态属性,属性值由类所有实例对象共享
(4) 方法的表示
- [可见性] 名称[(参数表)] [: 返回类型] [{约束特性}]
- 约束特性
- 查询操作: {isQuery = true}表示查询操作,{ isQuery = false}表示修改操作,缺省为修改操作。
- 多态性:{isPolymorphic = true}表示本操作允许多态,即可被子类中相同定义形式的操作所覆盖。
- 并发性:{concurrency = sequential} 任一时刻只有一个对象调用可执行。{concurrency = guarded} 并行线程可同时调用多个对象的本操作,但同一时刻只允许一个调用执行。{concurrency = concurrent} 并行线程可以同时调用多个对象的本操作且这些调用可并发执行
- 异常:操作在执行过程中可能引发异常
(5) 接口
接口是一种不包含操作实现部分的特殊类,形式分为:
供给接口: 对外提供的接口
需求接口: 需要使用的接口
(6) 类间关系
1. 关联
表示类间的逻辑联系
- 多重性:位于关联端的类可以有多少个实例对象与另一端的类的单个实例对象相联系
- 角色名:参与关联的类对象在关联关系中扮演的角⾊或发挥的作用
- 约束特性:针对参与关联的对象或对象集的逻辑约束
2. 聚合与组合
- 聚合关系(Aggregation):部分类对象是多个整体类对象的组成,例如一名学生可同时参与多个兴趣小组
- 组合关系(Composition):部分类对象只能位于一个整体类对象中,一旦整体类对象消亡,部分类对象也不能苟活
3. 依赖
有语义上关系且一个类对象变化会导致另一类对象作相应修改,形式有以下几类:
- 使用:A类使用B类的某项服务
- 追踪:B类的变化导致A类的变化
- 精化:A类是在B类基础上引进设计、决策等形成
- 实现:A类给出了B类元素的实现
- 派生:A类可通过B类推导或计算出
4. 实现
表示一个类实现了另一个类中定义的对外接口,是一种特殊依赖关系。典型应用为:一个具体类实现一个接口
5. 继承
子类继承父类所有可继承的特性,且可通过添加新特性或覆盖(override)父类中的原有特性。父类抽象多个子类中公共特性,从而避免冗余、简化操作。
对象图
**对象图是系统中的对象在运行过程中的静态瞬时快照,是类图在系统的运行过程中某个时刻点上或某一时间段内的实例化样本。**结点表示对象,边表示对象间的链接,例如类图中的一个类在对象图中可表现为多个活跃的对象实例;对象图的链接边是类图中关联边的实例化;类图中的其他边,如继承、依赖等在对象图中则无从表现。
状态图
(1) 功能
描述实体(对象、系统)在事件刺激下的反应式动态行为及其导致的状态变化,并刻画了实体的可能状态、每个状态下可响应事件、响应动作、状态迁移。
(2) 构成
状态:对象属性取值构成的一个约束条件,不同状态下对象对事件的响应行为完全一样
事件:某时刻点发生、需要关注的瞬时刺激或触动
- 消息型事件(同步):其他对象发来消息
- 信号型事件(异步):其他对象传来异步信号
- 时间型事件:到达特定时间点
- 条件型事件:对象属性取值满足特定条件
动作(action):计算过程,位于迁移边上,简单,执行时间短
活动(activity):计算过程,位于状态中,复杂、执行时间长
需求分析过程
分析和确定软件需求优先级
(1) 软件需求重要性
分为核心软件需求和外围软件需求:
核心软件需求在解决问题方面起到举足轻重的作用,提供了软件系统所特有的功能和服务,体现了软件系统的特⾊和优势。外围软件需求提供了次要、辅助性的功能和服务。
(2) 软件需求优先级
有些软件需求需要优先实现,尽早交付给用户使用,以发挥其价值;有些软件需求可以滞后实现,晚点交付使用。在设置优先级时,应考虑以下的因素:
- 结合软件项目开发的具体约束,考虑不同软件需求的重要性,确定软件需求的实现优先级,确保在整个迭代开发中有计划、有重点地实现软件需求。
- 按照软件需求的重要性来确定其优先级。
- 按照用户的实际需要来确定软件需求的优先级。
(3) 用例分析和实现的次序
确保有序地开展需求分析、软件设计和实现工作,使得每次迭代开发有其明确的软件需求集,每次迭代开发结束之后可向用户交付他们所急需的软件功能和服务。应考虑:
- 结合软件开发的迭代次数、每次迭代的持续时间、可以投入的人力资源等具体情况
- 充分考虑相关软件需求项的开发工作量和技术难度等因素,确定需求用例分析和实现的先后次序
用例名称 | 用例标识 | 重要性 | 优先级 | 迭代次序 |
---|---|---|---|---|
监视老人 | UC-MonitorElder | 核心 | 高 | 第一次迭代 |
获取老人信息 | UC-GetElderInfo | 核心 | 高 | |
检测异常状况 | UC-CheckEmergency | 核心 | 高 | |
通知异常状况 | UC-NotifyEmergency | 核心 | 高 | 第二次迭代 |
自主跟随老人 | UC-FollowElder | 核心 | 高 | |
视频/语音交互 | UC-A&VInteraction | 外围 | 中 | 第三次迭代 |
控制机器人 | UC-ControlRobot | 外围 | 低 | |
提醒服务 | UC-AlertService | 外围 | 低 | 第四次迭代 |
用户登录 | UC-UserLogin | 外围 | 低 | |
系统设置 | UC-SetSystem | 外围 | 低 |
分析和建立软件需求模型
分析和建立用例的交互模型
任务:分析和描述用例是如何通过一组对象之间的交互来完成的 步骤:
- 分析和确定用例所涉及的对象及其类
- 分析和确定对象之间的消息传递
- 绘制用例的交互图
(1) 分析和确定用例所涉及的对象及其类
软件需求用例的处理通常涉及三种不同类对象:边界类、控制类、实体类。这些类是在用例分析阶段所识别并产生的,通常将它们称为分析类。
1. 边界类
每个用例或者由外部执行者触发,或者需要与外部执行者进行某种信息交互,因而用例的业务逻辑处理需要有一个类对象来负责目标软件系统与外部执行者之间的交互。由于这些类对象处于系统的边界,需与系统外的执行者进行交互,因而将这些对象所对应的类称之为边界类。
边界类用于交互控制,处理外部执行者的输入数据,或者向外部执行者输出数据。也可以作为外部接口,如果外部执行者表现为其他的系统或者设备,那么边界类对象需要与系统之外的其他系统或设备进行信息交互。
2. 控制类
控制类对象作为完成用例任务的主要协调者。负责处理边界类对象发来的任务请求,对任务进行适当的分解,并与系统中的其他对象进行协同,以控制他们共同完成用例规定的任务或行为。
一般而言,控制类并不负责处理具体的任务细节,而是负责分解任务,并通过消息传递将任务分派给其他对象类来完成,协调这些对象之间的信息交互。
3. 实体类
用例所对应业务流程中的所有具体功能最终要交由具体的类对象来完成,这些类称之为实体类。
一般而言,实体类对象负责保存目标软件系统中具有持久意义的信息项,对这些信息进行相关的处理(如查询、修改、保存等),并向其他类提供信息访问的服务。实体类的职责是要落实目标软件系统中的用例功能,提供相应的业务服务。
(2) 分析和确定对象之间的消息传递
1. 确定消息名称
消息名称直接反映了对象间交互的意图(请求、通知),也体现了接收方对象所对应的类需承担的职责和任务,也即发送方对象希望接收方对象提供什么样的功能和服务。一般地,消息名称用动名词来表示,需求工程师应尽可能用应用领域中通俗易懂的术语来表达消息的名称和参数,以便于用户和需求工程师等能直观地理解对象间的交互语义。
2. 确定消息传递的信息
对象间的交互除了要表达消息名称和交互意图之外,在许多场合还需要提供必要的交互信息(通知和请求的内容),这些信息通常以消息参数的形式出现,也即一个对象在向另一个对象发送消息的过程中,需要提供必要的参数,以向目标对象提供相应的信息。在构建用例的交互图过程中,如果用例的业务流程能够明确相应的交互信息,那么就需要确定消息需附带的信息。通常,消息参数用名词或名词短语来表示。
(3) 绘制用例的交互图
用例的外部执行者应位于图的最左侧,紧邻其右的是用户界面或外部接口的边界类对象,再往右是控制类对象,控制类的右侧应放置实体类对象,它们的右侧是作为外部接口的边界类对象。对象间的消息传递采用自上而下的布局方式,以反映消息交互的时序先后。
(4) 交互图的工作流程
- 外部执行者与边界类对象进行交互以启动用例的执行
- 边界类对象接收外部执行者提供的信息,将信息从外部形式转换为内部形式,并通过消息传递将相关信息发送给控制类对象
- 控制类对象根据业务逻辑处理流程,产生和分解任务,与相关的实体类对象进行交互以请求完成相关的任务,或者向实体类对象提供业务信息,或者请求实体类对象持久保存业务逻辑信息,或者请求获得相关的业务信息
- 实体类对象实施相关的行为后,向控制类对象反馈信息处理结果
- 控制类对象处理接收到的信息,将处理结果通知边界类对象
- 边界类对象对接收到的处理结果信息进行必要的分析,将其从内部形式转换为外部形式,通过界面将处理结果展示给外部执行者
sequenceDiagram participant User participant LoginUI as <<boundary>><br>LoginUI participant LoginManager as <<controller>><br>Login participant UserLibrary as <<entity>><br>UserLibrary User->>LoginUI: goto LoginUI->>LoginManager: login(account, password) LoginManager->>UserLibrary: verifyUserValidity(account, password) UserLibrary-->>LoginManager: UserValidity LoginManager-->>LoginUI: LoginResult
分析和建立软件需求的分析类模型
(1) 确定分析类
用例模型中的外部执行者应该是分析类图中的类。在各个用例的顺序图,如果该图中出现了某个对象,那么该对象所对应的类属于分析类,应出现在分析类图中。可以根据用例图来确定分析类,也可以根据交互图来确定分析类。
软件需求用例的处理通常涉及三种不同类对象:边界类、控制类、实体类。这些类是在用例分析阶段所识别并产生的,通常将它们称为分析类。
(2) 确定分析类的职责
每一个分析类都有其职责,需提供相关的服务。对象接收的消息与其承担的职责之间存在一一对应关系,即如果一个对象能够接收某项消息,它就应当承担与该消息相对应的职责。可用类的方法名来表示分析类的职责,并采用简短的自然语言来详细刻画类的职责。
(3) 确定分析类的属性
分析类具有哪些属性取决于该类需要持久保存哪些信息。用例顺序图中的每个对象所发送和接收的消息中往往附带有相关的参数,这意味着发送或接收对象所对应的类可能需要保存和处理与消息参数相对应的信息,因而可能需要与此相对应的属性。
(4) 确定分析类之间的关系
类之间的关系有多种形式,包括继承、关联、聚合和组合、依赖等等,具体可以根据以下的策略进行分析:
- 在用例的顺序图中,如果存在从类A对象到类B对象的消息传递,那么意味着类A和B间存在关联、依赖、聚合或组合等关系。
- 如果经过上述步骤所得到的若干个类之间存在一般和特殊的关系,那么可对这些分析类进行层次化组织,标识出它们间的继承关系。
(5) 绘制分析类图
经过上述的步骤之后,绘制出系统的分析类图,建立分析类模型。直观描述了系统中的分析类、每个分析类的属性和职责、不同分析类之间的关系(如果系列规模较大,分析类的数量多,关系复杂,难以用一张类图来完整和清晰地表示,那么可以分多个子系统来绘制分析类图)。
分析和建立软件需求的状态模型
用UML的状态图来描述这些对象的状态模型,以刻画对象拥有哪些状态、对象的状态如何受事件的影响而发生变化。注意以下两点:
- 状态模型是针对对象而言的,而非针对分析类
- 需求工程师无需为所有的类对象建立状态模型,只需针对那些具有复杂状态的对象建立状态模型
软件需求文档化及评审
软件需求文档模板
分析软件需求的输出
- **软件原型:**以可运行软件的形式,直观地展示了软件的业务工作流程、操作界面、用户的输入和输出等方面的功能性需求信息
- **软件需求模型:**以可视化的图形方式,从多个不同的视角,直观地描述了软件的功能性需求,包括用例模型、用例的交互模型、分析类模型、状态模型等
- **软件需求文档:**以图文并茂的方式,结合需求模型以及需求的自然语言描述,详尽刻画了软件需求,包括功能性和非功能性软件需求,软件需求的优先级列表等
软件体系结构设计
软件体系结构的设计元素
(1) 构件
构件是构成体系结构的基本功能部件,是软件系统中的物理模块,具有特定的功能和精确定义的对外接口,外界可通过接口来访问它,其特点为:
- 可分离:一个或数个可独立部署执行码文件
- 可替换:构件实例可被其它任何实现了相同接口的另一构件实例所替换
- 可配置:可通过配置机制修改构件配置数据,影响构件对外服务的功能或行为
- 可复用:构件可不经源代码修改,无需重新编译,即可应用于多个软件项目或软件产品
例如.dll
文件和.jar
文件,不是源程序,而是可运行的二进制代码,是客观物理存在的(即有实际的文件),表示为UML:
(2) 连接件
连接件表示软构件之间的连接和交互关系。每个软构件并非孤立,它们之间通过连接进行交互,为了交换数据、获得服务。软构件之间的典型交互方式主要有:过程调用、远程过程调用(Remote Procedure Call,RPC)、消息传递、事件通知和广播、主题订阅等等。构件通过接口对外提供服务,或与其他构件进行交互,构件有两种接口:
- 供给接口,是对外提供的接口
- 需求接口,请求其他构件帮助所需的接口
构件的实现与构件的接口相分离,构件开发者可自由选择实现方法,只要它实现供给接口中的操作及属性。两个构件的实现遵循相同接口定义,它们就可自由替换。构件之间的连接和交互示例图及表示如下:
(3) 约束
约束是组件中的元素应满足的条件以及组件经由连接件组装成更大模块时应满足的条件。组件的元素应当满足以下几点:
- 高层次软件元素可向低层次软件元素发请求,低层次软件元素完成计算后向高层次发送应答,反之不行
- 每个软件元素根据其职责位于适当的层次,不可错置,如核心层不能包含界面输入接收职责
- 每个层次都是可替换的,一个层次可以被实现了同样的对外服务接口的层次所替代
软件体系结构的不同视图
(1) 逻辑视图——包图
包图模型包含一组具有逻辑关联的UML模型元素。包间关系可以分为:构成关系、依赖关系。包图可描述软件系统的高层结构,包图以结构化、层次化方式组织、管理大型的软件模型,使得分别处理不同包的开发团队之间的相互干扰程度降至最低。
(2) 逻辑视图——构件图
构件图可以描述软件系统中构件及构件间的关系,主要用来描述软件系统中构件的接口定义及构件间的依赖关系,以便评估软件变更的影响范围;描述软件系统或其中某个局部的构件化设计,为在后续开发阶段实现构件化的软件模块订立设计规约。
(3) 开发视图
开发视图主要包括:各软件要素源代码的程序分包及目录结构,采用的类库、中间件和框架,它们与逻辑视图中各个软件元素之间的映射关系。
(4) 部署视图
部署图:软件安装部署的物理机器及其连接,各个件元素在这些机器上的部署位置,表示软件系统可执行工件在运行环境中的分布情况。有两种部署图:逻辑层面的描述性部署图和物理层面的实例性部署图。
- 逻辑层面的描述性部署图描述软件的逻辑布局
- 物理层面的实例性部署图针对具体运行环境和特定的系统配置描述软件系统的物理部署情况
下图分别为描述性部署图和实例性部署图:
(5) 运行视图
运行视图描述软件运行时进程、线程的划分,它们之间的并发和同步,它们与逻辑视图和开发视图之间的映射关系,可用UML的活动图、对象图来表示。
软件设计模式
设计模式根据不同抽象层次可以分为:
- 体系结构风格:面向整个软件系统
- 构件设计模式:面向子系统或者构件
- 实现设计模式:针对子系统或构件中的某个特定问题
软件体系结构风格
面向整个软件系统,在抽象层次给出软件体系结构的结构化组织方式,提供一些预定义的子系统或者构件,规定其职责,明确它们之间的相互关系、协作方式的规则或指南,需要针对不同的问题采用不同的体系结构模式。
(0) 需求设计、软件体系结构设计、详细设计三者关联
- 软件需求是体系结构设计的基础和驱动因素,体系结构是以软件需求实现为目标的软件设计蓝图
- 体系结构设计为详细设计提供可操作指导,详细设计是对体系结构设计中设计要素的局部设计
(1) 分层体系结构模式
1. 基本概念
该模式将软件系统按照抽象级别逐次递增或递减的顺序,组织为若干层次,每层由一些抽象级别相同构件组成
典型层次示例:
- 顶层:直接面向用户提供软件系统的交互界面
- 底层:则负责提供基础性、公共性的技术服务,它比较接近于硬件计算环境、操作系统或数据库管理系统
- 中间层:介乎二者之间,负责具体的业务处理
2. 模式约束
层次间的关系
- 每层为其紧邻上层提供服务,使用紧邻下层所提供的服务
- 上层向下层发出服务请求,下层为上层反馈服务结果
- 下层向上层提供事件信息,上层对下层通知做出处理
服务接口的组织方式
- 层次中的每个构件分别公开其服务接口
- 每个层次统一对外提供整合的服务接口
3. 特点
- 松耦合:减低整个软件系统的耦合度
- 可替换:一个层次可以有多个实现实例
- 可复用:层次和整个系统可重用
- 标准化:支持体系结构及其层次、接口的标准化
(2) 管道和过滤器模式
1. 基本概念
- 将软件功能实现为一系列处理步骤,每个步骤封装在一个过滤器构件中;
- 相邻过滤器间以管道连接,一个过滤器的输出数据借助管道流向后续过滤器,作为其输入数据;
- 软件系统的输入由数据源(数据库、文件、其他软件系统、物理设备等)提供;软件最终输出由源自某个过滤器的管道流向数据汇(data sink)
例如编译器,采用的就是一个典型的管道/过滤器风格:
2. 模式约束
过滤器与管道之间的协作方式
- 过滤器以循环的方式不断地从管道提取输入数据,并将其输出数据压入管道,称为主动过滤器
- 管道将输入数据压入到位于其目标端过滤器,过滤器被动地等待数据,称为被动过滤器
- 管道负责提取位于其源端过滤器的输出数据
设计考虑
- 如果管道连接的两端均为主动过滤器,那么管道必须负责它们之间的同步,典型的同步方法是先进先出缓冲器
- 如果管道的一端为主动过滤器,另一端为被动过滤器,那么管道的数据流转功能可通过前者直接调用后者来实现
3. 特点
该模式可以自然地解决具有数据流特征的软件需求,且可独立地更新、升级过滤器来实现软件系统的扩展和进化。
(3) 黑板风格
1. 基本概念
将软件系统划分为黑板、知识源和控制器三类构件
- 黑板:负责保存问题求解过程中的状态数据,并提供这些数据的读写服务
- 知识源:负责根据黑板中存储的问题求解状态评价其自身的可应用性,进行部分问题求解工作,并将此工作的结果数据写入黑板
- 控制器:负责监视黑板中不断更新的状态数据,安排(多个)知识源的活动。
2. 模式约束
控制构件通过观察黑板中的状态数据来决定哪些知识源对后续的问题求解可能有所贡献,然后调用这些知识源的评价功能以选取参与下一步求解活动的知识源。被选中的知识源基于黑板中的状态数据将问题求解工作向前推进,并根据结果更新黑板中的状态数据。控制构件不断重复上述控制过程,及至问题求解获得满意的结果。
3. 特点
该模式可灵活升级和更换知识源和控制构件,知识源的独立性和可重用性好,因为知识源之间没有交互,除此之外软件系统具有较好的容错性和健壮性,知识源的问题求解动作是探索性的,允许失败和纠错。
(4) MVC风格
1. 基本概念
主要分为三个构件:
- 模型构件,负责存储业务数据并提供业务逻辑处理功能
- 视图构件,负责向用户呈现模型中的数据
- 控制器,在接获模型的业务逻辑处理结果后,负责选择适当的视图作为软件系统对用户的界面动作的响应
2. 模式约束
- 创建视图,视图对象从模型中获取数据并呈现用户界面,视图接受界面动作,将其转换为内部事件传递给控制器。所有视图在接获来自模型的业务数据变化通知后向模型查询新的数据,并据此更新视图。
- 控制器将用户界面事件转换为业务逻辑处理功能的调用。控制器根据模型的处理结果创建新的视图、选择其他视图或维持原有视图
- 模型进行业务逻辑处理,将处理结果回送给控制器,必要时还需将业务数据变化事件通知给所有视图
(5) SOA风格
将软件系统的软构件抽象为一个个的服务(Service),每个服务封装了特定的功能并提供了对外可访问的接口。任何一个服务既可以充当服务的提供方,接受其他服务的访问请求;也可充当服务的请求方,请求其他服务为其提供功能。任何服务需要向服务注册中心进行注册登记,描述其可提供的服务以及访问方式,才可对外提供服务。
(6) 消息总线
包含了一组软构件和一条称为“消息总线”的连接件来连接各个软构件。消息总线成为软构件之间的通信桥梁,实现各个软构件之间的消息发送、接收、转发、处理等功能。每一个软构件通过接入总线,实现消息的发送和接收功能
(7) C/S风格
Client/Server 的简称,客户机/服务器模式。C/S 结构充分利用客户端的硬件设施,将很多的数据处理工作在客户端完成,故数据处理能力比较强大,对一些复杂的业务流程,也容易实现。
两层C/S:
三层C/S:
(8) B/S
浏览器/服务器(Browser/Server,简称 B/S)风格就是上述三层应用结构的一种实现方式,其具体结构为:浏览器/Web服务器/数据库服务器。
(9) 总结
类别 | 特点 | 典型应用 |
---|---|---|
管道/过滤器风格 | 数据驱动的分级处理,处理流程可灵活重组,过滤器可重用 | 数据驱动的事务处理软件,如编译器、Web服务请求等 |
层次风格 | 分层抽象、层次间耦合度低、层次的功能可重用和可替换 | 绝大部分的应用软件 |
MVC风格 | 模型、处理和显示的职责明确构件间的关系局部化,各个软构件可重用 | 单机软件系统,Web应用软件系统 |
SOA风格 | 以服务作为基本的构件,支持异构构件之间的互操作,服务的灵活重用和组装 | 部署和运行在云平台上的软件系统 |
消息总线风格 | 提供统一的消息总线,支持异构构件之间的消息传递和处理 | 异构构件之间消息通信密集型的软件系统 |
用户界面设计
软件详细设计
输入软件体系结构设计、用户界面设计、软件需求,进行细化和精化,具体对象为子系统、构件、关键设计类和界面类,最后获得高质量、面向实现的设计模型,该模型直接支持编码和程序设计。
活动图
(1) 基本概念
描述实体为完成某项功能而执行的操作序列,其中某些操作或其子序列存在并发和同步
(2) 构成
- 活动点:表示计算过程
- 决策点:根据条件进行活动决策
- 边:表示控制流或信息流。控制流:表示一个操作完成后对其后续操作的触发;信息流:刻画操作间的信息交换
- 并发控制:控制流经此节点后分叉(Fork)成多条可并行执行的控制流,或多条并行控制流经此节点后同步合并(Join)为单条控制流
- 泳道:将活动图用形如游泳池中的泳道分隔成数个活动分区,每个区域由一个对象或一个控制线程负责,每个活动节点应位于负责执行该活动的对象或线程所在的区域内。带泳道的活动图更清晰地表示了对象或线程的职责、它们之间的分工、协同和同步。
(3) 绘制原则
从决策点出发的每条边上均应标注条件,且这些条件必须覆盖完整且互不重叠。必须确保分叉和汇合节点之间的匹配性,对任一分叉节点,其导致的并发控制流必须最终经由一个汇合节点进行控制流的同步和合并。一张活动图可以浓缩成另一活动图中的单个活动节点,前者称为子活动图,后者称为父活动图
用例设计
给出用例的具体实现解决方案,描述用例是如何通过各个设计元素(包括子系统、软构件、设计类等)的交互和协作来完成的。需要产出用例设计的顺序图、设计类图。
设计用例实现方案
(1) 基本步骤
基础:
- 分析每个用例的UML交互图,它是开展用例设计的依据
- 考虑体系结构和用户界面设计要素,它们是用例实现参与者
- 明确各个设计元素(如对象类、构件等)的职责
方法:
在需求分析阶段用例交互图的基础上,将交互图的分析类转化为用例实现的设计类,同时引入体系结构设计和用户界面设计所生成的设计元素,共同形成关于用例实现的交互模型
结果:
- 生成用例实现的顺序图
- 引入更多的设计元素,如构件、子系统、类等
- 精化更多的设计细节,如接口、方法、交互等
(2) 分析类与设计元素间对应关系
一个分析类的一项职责由一个设计元素的单项操作完整地实现
一个分析类的一项职责由一个设计元素的多项操作来实现
一个分析类的一项职责由多个设计元素协同完成
(3) 示例
1. “用户登录”用例
2. “系统设置”用例
3. “提醒服务”用例
4. ”远程控制机器人“用例
构造设计类图
(1) 基本概念
需要基于用例实现方案给出详细设计模型中的设计类图,类图中的设计元素可以为子系统、构件和UML类,用不同的构造型或者相应图符来表示。
对UML类图稍作扩充以表示详细设计类图,允许在类图中出现子系统和构件,它们的类别可以采用不同的UML构造型或者不同的图元符号来表示。
(2) 构造方法
- 创建初始的设计类:从分析类图、体系结构图、用例实现的顺序图寻找
- 确定设计类的职责:每个设计类都有职责,取决于对交互图中消息的响应
- 确定设计类间的关系:如果A与B有消息,那么它们间有关系:关联、聚合和组合
- 确定设计类的属性:需要保存哪些数据项、其他对象类信息
- 形成整体设计类图:模块化、职责和功能单一化原则,调整和优化
另外,需要确保设计类图与(需求)分析类图之间、设计类图与用例设计模型之间的一致性:分析类图中的类在设计类图中有相应的对应物,用例设计模型中的设计元素(主要是指参与用例实现的对象、对象间的消息传递)在设计类图中要有相应的对应物(主要是指设计类及其方法)
如下图,为根据用例设计的交互图(顺序图)构造出设计类图:
类设计
给出每个设计类的具体实现细节,包括类的属性定义、方法的实现算法等,使得程序员能够基于类设计给出这些类的实现代码。需要产出详细的类属性方法和类间关系设计的类图、描述类方法实现算法细节的活动图、必要的状态图(可选)。
确定类的可见范围
“可见范围”的定义为:如果类仅仅被其所在的包所使用,那么该类是“私有的”,否则是“公开的”。应尽量缩小类的可见范围,除非确有必要,否则应将类“隐藏”于包的内部。
- public: 公开级范围,软件系统中所有包中的类均可见和可访问该类
- protected:保护级范围,只对其所在包中的类以及该类的子类可见和访问
- private:私有级范围,只对其所在包中的类可见和访问
精化类间关系
类间关系的语义强度从高到低依次是:继承,组合,聚合,(普通)关联,依赖。定义类间关系时,应当遵循以下原则:
- “自然抽象”原则,类间关系应该自然、直观地反映软件需求及其实现模型
- “强内聚、松耦合”的原则,尽量采用语义连接强度较小的关系
1:m,即一对多的类间关系:
精化用户界面类间的关系:
精化关键设计类间的关系:
精化类的属性和方法
(1) 精化类属性
类属性用业务领域的名词或者名词短语来命名。类属性的可见范围可以分为public、protected、private。又是需要结合类关系来精化类属性设计:
- 如果类A与类B间存在1:1关联或聚合(非组合)关系,那么在A中设置类型为B的指针或引用(reference)的属性
- 如果类A到类B间存在1:n关联或聚合(非组合)关系,那么在A中设置一个集合类型(如列表等)的属性,集合元素的类型为B的指针或引用
- 如果类A与类B间存在1:1的组合关系,那么在A中设置类型为B的属性
- 如果类A到类B间存在1:n的组合关系,那么在A中设置一个集合类型(如列表等)的属性,集合元素的类型为B
例如精化Robot类属性的设计:
private int velocity
:表示机器人的速度private int angle
:表示运动角度private int distance
:表示与老人的距离private int state
:表示运动状态,包括“IDLE”空闲状态、“AUTO”自主跟随状态、“MANNUAL”手工控制状态
(2) 精化类方法
细化和明确类中各个方法的以下设计信息:方法名称、参数表(含参数的名称和类型)、返回类型、作用范围、功能描述、实现算法、前提条件(pre-condition)、出口断言(post-condition)等。可以用活动图来描述方法的详细设计,下图分别为Login()
、detectFallDown()
方法的描述:
构造类对象的状态图
如果一个类的对象具有较为复杂的状态,在其生命周期中需要针对外部和内部事件实施一系列的活动以变迁其状态,那么可以考虑构造和绘制类的状态图。如果某个类在实现其职责过程中需要执行一系列的方法、与其他的对象进行诸多的交互,那么可以考虑构造和绘制针对该类某些职责的活动图。如Robot类对象的状态图:
数据设计
对软件所涉及的持久数据及其操作进行设计,明确持久数据的存储方式和格式,细化数据操作的实现细节。需要产出描述数据设计的类图、描述数据操作的活动图。
确定永久数据与确定数据存储和组织方式
(1) 类内内容
根据对需求的理解来确定哪些数据需要永久保存,如用户的账号和密码、系统设置信息。然后确定将数据存储在何处,如数据文件中、数据库中。若为数据文件,则需要确定数据存储的组织格式,以便将格式化和结构化的数据存放在数据文件之中;若为数据库,则需要设计支持数据存储的数据库表。以数据库表为例,首先确定设计模型中需要持久保存的类的对象及其属性,然后明确面向对象设计模型与关系数据库模型的对应关系、类对应于 “表格”(table)、对象对应于“记录”(record)、属性对应于表格中的“字段”(field),如下:
(2) 类间关系
1. 关联关系
1:1、1:n关联关系的映射:假设类C1、C2对应的表格分别为T_C1、T_C2,只要将T_C1中关键字段纳入T_C2中作为外键,就可表示从T_C1到T_C2间的1:1、1:n关联关系:
n:m关联关系的映射:在T_C1、T_C2间引进新交叉表格T_Intersection,将T_C1、T_C2关键字段纳入T_Intersection中作为外键,在T_C1与T_Intersection之间、T_C2与T_Intersection之间建立一对多关系:
2. 继承关系
假设C1是C2的父类,数据库表设计有两种方法。第一种方法:将T_C1中的所有字段全部引入至T_C2:
这种方法浪费了持久存储空间,容易因数据冗余而导致数据不一致性。因此可以使用第二种方法:仅将T_C1中关键字字段纳入T_C2中作为外键:
获取C2对象的全部属性,需要联合T_C2中的记录和对应于外键值的T_C1中的某条记录。这种方法避免了数据冗余,但在读取C2对象时性能不如前种方法。
设计数据操作
主要有写入、查询、更新和删除四类基本操作以及由它们复合而成的业务,以及数据验证操作,其负责验证数据的完整性、相关性、一致性等等。例如:boolean insertUser(User)
、boolean deleteUser(User)
、boolean updateUser(User)
、User getUserByAccount(account)
、boolean verifyUserValidity(account, password)
等。
子系统/构件设计
(1) 子系统设计
需要确定子系统内部结构,设置包含于其中的更小粒度子系统、构件和设计类,明确它们之间的协作关系,确保它们能够协同实现子系统接口规定的所有功能和行为。在这一步中,需要完成的操作有:
- 细化子系统内部的细节,如设计元素、关联和交互
- 对子系统内部的结构进行建模
- 对子系统内部各个设计元素之间的协作进行建模
最后需要产出子系统的包图、构件图、顺序图、活动图、类图。
1. 精化子系统内部设计元素
需要输出一组描述子系统内部设计元素交互的UML顺序图,例如RobotController”子系统实现“自主跟随老人”功能和职责的顺序图:
2. 构造子系统的设计类图
需要输出基于子系统设计的UML交互图,其详细描述了子系统功能和职责的实现方式。另外,要推导出子系统的设计类图,在该设计类图中,显式区分子系统内部的设计元素与位于子系统之外、为子系统提供服务的其他设计元素。例如,“RobotController”子系统的类图:
3. 构造子系统的状态图和活动图
如果子系统或其内部设计元素具有明显状态特征,那么绘制和分析其UML状态图。构造子系统及其设计元素的活动图来理解和分析子系统是如何实现的。
(2) 构件设计
需要定义构件内部的设计元素及其协作方法,内部设计元素可以是子构件,也可以是粒度更细的类。在这一步中,需要完成的操作有:
- 细化构件的内部细节,如子构件、类等
- 对构件内部的结构进行建模
- 对构件内部各个设计元素之间的协作进行建模
最后需要输出构件图、类图、顺序图、活动图等。
若干面向对象的设计模式
设计模式概述
(1) 软件模式的层次分类
类别 | 描述 |
---|---|
体系结构模式 (Architecture Styles) | - 是系统的高层次策略,涉及到大尺度的组件以及整体性质 - 可作为具体软件体系结构的模板,是开发一个软件系统时的基本设计决策 - 规定了系统范围结构特性,架构模式的好坏影响到总体布局和框架性结构 |
设计模式 (Design Patterns) | - 是中等尺度的结构策略。实现了一些大尺度组件的行为和它们之间的关系 - 设计模式定义出子系统或组件的微观结构 - 模式的好坏不会影响到系统的总体布局和总体框架 |
代码模式 (Idioms) | - 是特定的范例和与特定语言有关的编程技巧 - 处理特定设计问题的实现,关注设计和实现方面 - 模式的好坏会影响中等尺度组件的内部、外部的结构或行为的底层细节 |
(2) 模式
设计模式是关于特定场景下解决一般设计问题的类和相互通信的对象的描述,展示了对典型性问题的普遍、独立于领域的解决方案。作为软件模式的一种,设计模式专注于软件开发中的设计层面,而软件模式则涵盖了更广泛的范畴,包括架构模式、分析模式和过程模式等,实际上在软件生存期的每一个阶段都存在一些被认同的模式。软件模式可以视为对软件开发这一特定“问题”的“解法”的统一表示,即在一定条件下出现的问题及其相应的解决方案。
设计模式通常包含几个基本要素:
- 模式名称:概括描述了模式的问题、解决方案和效果
- 问题:解释何时使用模式及设计问题的背景
- 解决方案:详细描述设计的组成部分、它们之间的关系及职责
- 效果:评估模式应用的结果及需权衡的因素
设计模式提供了一个标准的术语系统,且具体到特定的情景。例如,单例设计模式意味着使用单个对象,这样所有熟悉单例设计模式的开发人员都能使用单个对象,并且可以通过这种方式告诉对方,程序使用的是单例模式。设计模式已经经历了很长一段时间的发展,它们提供了软件开发过程中面临的一般问题的最佳解决方案。学习这些模式有助于经验不足的开发人员通过一种简单快捷的方式来学习软件设计。
根据设计模式的目的和范围(即模式主要是用于处理类之间关系还是处理对象之间的关系),有以下分类:
范围 | 目的 | ||
---|---|---|---|
创建型 | 结构型 | 行为型 | |
类 | Factory Method | Adapter (类) | Interpreter Template Method |
对象 | Abstract Factory Builder Prototype Singleton | Adapter (对象) Bridge Filter、Criteria Pattern Composite Decorator Façade外观 Flyweight享元 Proxy | Chain of Responsibility Command Iterator Mediator中介者 Memento Observer State Strategy Visitor |
创建型模式
创建型模式关注对象的创建,抽象和封装了对象的实例化过程,分离了对象创建和对象使用,作为客户程序仅仅需要去使用对象,而不再关心创建对象过程中的逻辑。帮助系统独立于创建、组合和表示它的那些对象。创建型模式有两个重要的特点:
- 客户不知道对象的具体类是什么(除非看源代码);
- 隐藏了对象实例是如何被创建和组织的。
当想要使用new运算符的时候,就可以考虑创建型模式。创建型类模式使用继承机制改变被实例化的类,创建型对象模式则将实例化工作委托给另一个对象来完成。常见的创建型模式有Factory Method ,Abstract Factory,Builder,Prototype,Singleton。
(1) 工厂方法(Factory Method)
1. 基本概念
在工厂方法模式中,工厂父类负责定义创建产品对象的公共接口,而工厂子类则负责生成具体的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成,即通过工厂子类来确定究竟应该实例化哪一个具体产品类。
2. 示例1
假设正在开发一款物流管理应用。最初版本只能处理卡⻋运输, 因此大部分代码都在位于名为卡车的类中。一段时间后, 这款应用变得极受欢迎。每天都能收到十几次来自海运公司的请求,希望应用能够支持海上物流功能。这可是个好消息。 但是代码问题该如何处理呢?目前, 大部分代码都与卡车类相关。在程序中添加轮船类需要修改全部代码。更糟糕的是,如果以后需要在程序中支持另外一种运输方式,很可能需要再次对这些代码进行大幅修改。最后, 将不得不编写繁复的代码,根据不同的运输对象类, 在应用中进行不同的处理。
工厂方法模式建议使用特殊的工厂方法代替对于对象构造函数的直接调用 (即使用 new运算符)。不用担心,对象仍将通过 new运算符创建, 只是该运算符改在工厂方法中调用罢了。工厂方法返回的对象通常被称作“产品”。乍看之下, 这种更改可能毫无意义: 只是改变了程序中调用构造函数的位置而已。 但是,仔细想一下, 现在 可以在子类中重写工厂方法, 从而改变其创建产品的类型。有一点需要注意:仅当这些产品具有共同的基类或者接口时, 子类才能返回不同类型的产品,同时基类中的工厂方法还应将其返回类型声明为这一共有接口。
举例来说, 卡车Truck和 轮船Ship类都必须实现 运输Transport接口, 该接口声明了一个名为 deliver交付的方法。 每个类都将以不同的方式实现该方法: 卡⻋⾛陆路交付货物, 轮船走海路交付货物。 陆路运RoadLogistics类中的工厂方法返回卡⻋对象, 而 海路运输Sea-Logistics类则返回轮船对象。
3. 示例2
考虑这样一个系统,一个按钮工厂类可以返回一个具体的按钮实例,如圆形按钮、矩形按钮、菱形按钮等。在这个系统中,如果需要增加一种新类型的按钮,如椭圆形按钮,那么除了增加一个新的具体产品类之外,还需要修改工厂类的代码,这就使得整个设计在一定程度上违反了“开闭原则”。
现在对该系统进行修改,不再设计一个按钮工厂类来统一负责所有产品的创建,而是将具体按钮的创建过程交给专门的工厂子类去完成,通过先定义一个抽象的按钮工厂类,再定义具体的工厂类来生成圆形按钮、矩形按钮、菱形按钮等,它们实现在抽象按钮工厂类中定义的方法。这种抽象化的结果使这种结构可以在不修改具体工厂类的情况下引进新的产品,如果出现新的按钮类型,只需要为这种新类型的按钮创建一个具体的工厂类就可以获得该新按钮的实例,更加符合“开闭原则”。
(2) 建造者模式(Builder)
1. 基本概念
一种创建型设计模式, 使 能够分步骤创建复杂对象。 该模式允许使用相同的创建代码生成不同类型和形式的对象。假设有这样一个复杂对象, 在对其进行构造时需要对诸多成员变量和嵌套对象进行繁复的初始化工作。 这些初始化代码通常深藏于一个包含众多参数且让人基本看不懂的构造函数中; 甚至还有更糟糕的情况, 那就是这些代码散落在客户端代码的多个位置。
- Builder:抽象建造者为创建一个产品对象的各个部件指定抽象接口
- ConcreteBuilder:具体建造者实现了抽象建造者接口,实现各个部件的构造和装配方法,定义并明确它所创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象
- Director:指挥者负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其construct方法中调用建造者对象的部件构造与装配方法,完成复杂对象的建造
- Product:产品角色是被构建的复杂对象,包含多个组成部件。
2. 示例 如何创建一个房屋House对象? 建造一栋简单的房屋, 首先 需要建造四面墙和地板, 安装房⻔和一套窗户, 然后再建造一个屋顶。 但是如果 想要一栋更宽敞更明亮的房屋, 还要有院子和其他设施 (例如暖气、 排水和供电设备), 那又该怎么办呢?
最简单的方法是扩展 房屋基类, 然后创建一系列涵盖所有参数组合的子类。但最终将面对相当数量的子类。 任何新增的参数 (例如⻔廊类型) 都会让这个层次结构更加复杂。
另一种方法则无需生成子类。 可以在 房屋基类中创建一个包括所有可能参数的超级构造函数, 并用它来控制房屋对象。 这种方法确实可以避免生成子类, 但它却会造成另外一个问题。通常情况下, 绝大部分的参数都没有使用,这使得对于构造函数的调用十分不简洁。例如, 只有很少的房子有游泳池, 因此与游泳池相关的参数十之八九是毫无用处的。
构建者式建议将对象构造代码从产品类中抽取出来, 并将其放在一个名为构建者的独立对象中。该模式会将对象构造过程划分为一组步骤, ⽐如 buildWalls()
创建墙壁和buildDoor()
创建房⻔创建房⻔等。 每次创建对象时, 都需要通过建造者对象执行一系列步骤。 重点在于 无需调用所有步骤, 而只需调用创建特定对象配置所需的那些步骤即可。
主管Director进一步将用于创建产品的一系列建造者步骤调用抽取成为单独的主管类。 主管类可定义创建步骤的执行顺序, 而建造者则提供这些步骤的实现。严格来说, 程序中并不一定需要主管类。 客户端代码可直接以特定顺序调用创建步骤。 不过, 主管类中非常适合放入各种例行构造流程,以便在程序中反复使用。 此外, 对于客户端代码来说, 主管类完全隐藏了产品构造细节。 客户端只需要将一个建造者与主管类关联, 然后使用主管类来构造产品,就能从建造者处获得构造结果了。
结构模式
涉及到如何组合类和对象以获得更大结构的系统 ,就像搭积⽊,可以通过简单积⽊的组合形成复杂的、功能更为强大的结构。保证不会因应用系统的变化而要修改对象间的连接。结构型类模式采用继承机制来组合接口或实现,如可采用多重继承方法将两个以上的类组合成一个类,该类包含了所有父类的性质。这一模式有助于多个独立开发的类库协同工作。
结构型对象模式描述如何对一些对象进行组合,从而实现新功能的一些方法。由于可在运行时刻改变对象组合关系,所以对象组合方式具有更大的灵活性,而这种机制用静态类组合是不可能实现的。常见的结构型模式:Adapter(适配器), Bridge(桥接),Composite(组合),Proxy(代理),Decorator(装饰),Façade(外观),Flyweight(享元)
(1) 适配器模式(类)
1. 基本概念
适配器模式是一种结构型设计模式, 它能使接口不兼容的对象能够相互合作。
2. 示例
假如 正在开发一款股票市场监测程序, 它会从不同来源下载 XML 格式的股票数据, 然后向用户呈现出美观的图表。在开发过程中, 决定在程序中整合一个第三方智能分析函数库。 但是遇到了一个问题, 那就是分析函数库只兼容 JSON 格式的数据。可以修改程序库来支持 XML。 但是,这可能需要修改部分依赖该程序库的现有代码。 甚至还有更糟糕的情况, 可能根本没有程序库的源代码, 从而无法对其进行修改。
解决方案:可以创建一个适配器。 这是一个特殊的对象, 能够转换对象接口, 使其能与其他对象进行交互。
适配器模式通过封装对象将复杂的转换过程隐藏于幕后。 被封装的对象甚至察觉不到适配器的存在。 例如, 可以使用一个将所有数据转换为英制单位 (如英尺和英里) 的适配器封装运行于米和千米单位制中的对象。适配器不仅可以转换不同格式的数据, 其还有助于采用不同接口的对象之间的合作。 它的运作方式如下:
- 适配器实现与其中一个现有对象兼容的接口。
- 现有对象可以使用该接口安全地调用适配器方法。
- 适配器方法被调用后将以另一个对象兼容的格式和顺序将请求传递给该对象。
有时 甚至可以创建一个双向适配器来实现双向转换调用。回到股票市场程序。 为了解决数据格式不兼容的问题, 可以为分析函数库中的每个类创建将 XML 转换为 JSON 格式的适配器, 然后让客户端仅通过这些适配器来与函数库进行交流。 当某个适配器被调用时,它会将传入的 XML 数据转换为 JSON 结构, 并将其传递给被封装分析对象的相应方法。
对于类适配器,通过使用一个具体的Adapter类对Adaptee和Target进行匹配;可以使用多重继承对一个接口与另一个接口进行匹配。
(2) 装饰模式(Decorator)
装饰模式是一种结构型设计模式, 允许 通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为。
行为模式
行为型模式涉及到算法和对象间职责的分配。行为模式不仅描述对象或类的模式,还描述它们之间的通信模式(相互作用)。这些模式刻划了在运行时难以跟踪的复杂的控制流,使得设计者可以将注意力集中在对象间的联系方式,而不是控制流。通过行为型模式,可以更加清晰地划分类与对象的职责,并研究系统在运行时实例对象之间的交互。在系统运行时,对象并不是孤立的,它们可以通过相互通信与协作完成某些复杂功能,一个对象在运行时也将影响到其他对象的运行。
常见的行为型模式:Chain of Responsibility(职责链),Mediator(中介), Command(命令) ,Iterator (迭代器),Memento(备忘录) ,Observer(观察者),State(状态) ,Strategy(策略) ,Visitor(访问者),Interpret(解释器),Template(模板)
(1) Strategy模式
策略模式是一种行为设计模式, 它能让 定义一系列算法, 并将每种算法分别放入独立的类中, 以使算法的对象能够相互替换。
(2) Observer模式
观察者模式是一种行为设计模式, 允许 定义一种订阅机制, 可在对象事件发生时通知多个 “观察” 该对象的其他对象。
软件实现基础和编码
软件实现基础
(1) 软件实现的过程
(2) 软件实现与软件设计之间的关系
基于软件设计来开展软件实现:照软件设计模型和文档来进行编码
根据实现中发现的问题来纠正和完善软件设计:
- 设计不够详细,程序员需要进行进一步的软件设计和程序设计,才能编写出程序代码
- 设计考虑不周全,软件设计时没有认真考虑编码实现的具体情况(如程序设计语言和目标运行环境的选择),导致有些软件设计不能通过程序设计语言加以实现
代码编写
(1) 编写代码的任务
根据软件设计信息,借助于程序设计语言,编写出目标软件系统的源程序代码,开展程序单元测试、代码审查等质量保证工作
(2) 编写类代码
- 编写实现类的代码:**设计模型(如设计类图)**详细描述了软件系统中类的详细设计信息,包括可见性、类名、属性、方法等等。程序员需要将这些设计信息直接转换为用程序设计语言表示的实现结构和代码。
- 编写实现类方法的代码:基于类方法的设计描述(UML的活动图表示),程序员可以依此为依据来编写类方法的实现代码。
- 编写实现类间关联的代码:将类间关联关系的语义信息具体落实到相应类的程序代码中,即综合考虑关联关系的方向性、多重性、角色名和约束特性等信息来编写相关的类程序代码。
- 编写实现设计类间聚合和组合关系的代码:可以采用类似于实现关联关系的方法来编写实现聚合和组合关系的代码,根据多重性来设计相应类属性的数据结构。
- 编写实现接口关系的代码:类设计模型可能包含有表征类与接口之间实现关系的语义信息。诸多面向对象程序设计语言(如Java、C++等)提供了专门针对接口实现的语言机制,因而可以直接将接口设计信息转换为相应的程序代码。
- 编写实现继承关系的程序代码:面向对象程序设计语言(如Java、C++)提供了继承机制以及相应的语言设施。将设计模型中的类间继承关系用程序设计语言提供的语言机制来表示。
- 编写实现包的代码:用包(package)来组织和管理软件系统中的类。包是对软件系统中模块的逻辑划分,也可以将包视为是一种子系统。面向对象程序设计语言(如Java)提供了对包进行编程的语言机制,每个包对应于代码目录结构中的某个目录。
(2) 编写用户界面代码
(3) 编写数据设计代码
数据设计,定义了软件系统中需要持久保存数据及其组织(如数据库的表、字段)和存储(如数据库中的记录)方式设计了相应的类及其方法来读取、保存、更新和查询持久数据。
编码实现:
- 创建相应的数据库关系表格及其内部的各个字段选项等,确保它们满足设计的要求和约束
- 编写相应的程序代码来操作数据库,如增加、删除、更改、查询数据记录等
软件缺陷和调试
(1) 何为软件缺陷
软件缺陷是指软件制品中存在不正确的软件描述和实现。存在缺陷的软件制品不仅包括程序代码,而且还包括需求和设计的模型和文档。软件缺陷产生于软件开发全过程,只有有人介入的地方就有可能产生软件缺陷。任何人都有可能在软件开发过程中犯错误,进而引入软件缺陷。无论是高层的需求分析和软件架构缺陷还是底层的详细设计缺陷,它们最终都会反映在程序代码之中,导致程序代码存在缺陷。
(2) 软件缺陷的描述
(3) 软件缺陷的应对方法
1. 预防缺陷 通过运用各种软件工程技术、方法和管理手段,在软件开发过程中预防和避免软件缺陷,减少软件缺陷的数量,降低软件缺陷的严重程度。采用结对编程、严格的过程管理、必要的技术培训、CASE工具的使用等手段,起到预防缺陷的目的。
2. 容忍缺陷 增强软件的缺陷容忍度,借助于软件容错机制和技术,允许软件出现错误,但是在出现错误时软件仍然能够正常的运行。在高可靠软件系统的开发过程中,软件工程师通常需要提供容错模块和代码。显然这会增加软件开发的复杂度和冗余度。
3. 发现缺陷 通过有效的技术和管理手段来发现这些软件缺陷。例如,制定和实施软件质量保证计划、开展软件文档和模型的评 审、程序代码的走查、软件测试等工作。它们都可以帮助软件工程师找到潜藏在文档、模型和代码中的软件缺陷
4. 修复缺陷 通过一系列的手段来修复缺陷。采用程序调试等手段来找到缺陷的原因、定位缺陷的位置,进而修改存在缺陷的程序代码,将软件缺陷从软件制品中移除出去。
软件测试(精简版)
基本概念
(1) 任务
软件测试是运行软件或模拟软件的执行,发现软件缺陷的过程。需要注意,软件测试通过运行程序代码的方式来发现程序代码中潜藏的缺陷,这和代码走查、静态分析形成鲜明对比。另外软件测试的目的是为了发现软件中的缺陷。它只负责发现缺陷,不负责修复和纠正缺陷。
(2) 测试用例
软件测试需要合适的测试用例,测试用例包含4个元素:
- 输入数据:交由待测试程序代码进行处理的数据
- 前置条件:程序处理输入数据的运行上下文,即要满足前置条件
- 测试步骤:程序代码对输入数据的处理可能涉及到一系列的步骤,其中的某些步骤需要用户的进一步输入
- 预期输出:程序代码的预期输出结果
例如,“用户登录”模块单元的测试用例设计:
- 输入数据:用户账号=“admin”,用户密码=“1234”
- 前置条件:用户账号“admin”是一个尚未注册的非法账号,也即“T_User”表中没有名为“admin”的用户账号。
- 测试步骤:首先清除“T_User”表中名为“admin”的用户账号;其次用户输入“admin”账号和“1234”密码;第三,用户点击界面的确认按钮;最后,系统提示“用户无法登录系统”的信息
- 预期输出:系统将提示“用户无法登录系统”的提示信息
(3) 活动及流程
测试类型 | 测试对象 | 测试技术 | 测试内容 | 设计阶段及依据 |
---|---|---|---|---|
单元测试 | - 软件基本模块单元 - 过程、函数、方法、类 | 大多采用白盒测试技术 | - 模块接口测试 - 模块局部数据结构测试 - 模块独立执行路径测试 - 模块错误处理通道测试 - 模块边界条件测试 | 详细设计阶段可以设计单元测试用例及计划 |
集成测试 | - 软件模块之间的接口 - 过程调用、函数调用、消息传递、远程过程调用 | 采用黑盒测试技术 | - 过程调用 - 函数调用 - 消息传递 - 远程过程调用 - 网络消息 | 概要设计阶段可以设计集成测试用例及计划 |
确认测试 | - 软件的功能和性能 - 判断目标软件系统是否满足用户需求 | 采用黑盒测试技术 | - 根据软件需求规格说明书进行测试 | 需求分析阶段可以设计确认测试用例及计划 |
(4) 后续工作
修改程序可能会引入新的错误,原先“正常”的程序现在变得“不正常”,为此引入回归测试。回归测试目的是验证软件新版本是否从正常状态回归/退化到不正常状态。在回归测试中,再次运行所有的测试用例来发现缺陷,单元测试是回归测试的基础。调试技术包括蛮干法/ 测试、回溯法、归纳法、排除法。
软件测试需要输出:
- 软件测试计划
- 软件测试报告,记录软件测试情况以及发现的缺陷
- 软件单元测试报告
- 软件集成测试报告
- 软件确认测试报告
- 系统测试报告等
- 每个报告详细
软件测试技术
测试用例设计
可以根据以下两种思路来进行测试用例的设计:
穷举设计:
选择测试:
白盒测试
(1) 基本概念
白盒测试需要根据程序单元内部工作流程来设计测试用例。其目的是发现程序单元缺陷,即运行待测试的程序,检验程序是否按内部工作流程来运行的,如果不是则存在缺陷。因此必须了解程序的内部工作流程才能设计测试用例。
(2) 测试用例设计
1. 语句覆盖法
使得程序中的每一个语句至少被遍历一次,例如可以设计用例:$A=2,B=0,X=3$。
2. 判定覆盖法(分支)
使得程序中每一个分支至少被遍历一次。例如对于路径ace,设置$A=3,B=0,X=1$;对于路径abd,设置$A=1,B=0,X=0$。
3. 条件覆盖
使得每个判定的条件获取各种可能的结果。
在a点,$A>1, A\leq1,B=0,B\neq0$,在b点,$A=2, A\neq2, X>1, X\leq1$。例如对于路径ace,设置$A=2,B=0,X=4$;对于路径abd,设置$A=1,B=1,X=1$。
4. 判定/条件覆盖
使得判定中的条件取得各种可能的值,并使得每个判定取得各种可能的结果。例如对于路径ace,设置$A=2,B=0,X=4$;对于路径abd,设置$A=1,B=1,X=1$。
5. 条件组合覆盖
使得每个判定条件的各种可能组合都至少出现一次,例如,要求
- $ A > 1 , B = 0 $
- $ A > 1 , B \neq 0 $
- $ A \leq 1 , B = 0 $
- $ A \leq 1 , B \neq 0 $
- $ A = 2 , X > 1 $
- $ A = 2 , X \leq 1 $
- $ A \neq 2 , X > 1 $
- $ A \neq 2 , X \leq 1 $
则有测试用例
- $ A = 2 , B = 0 , X = 4 $
- $ A = 2 , B = 1 , X = 1 $
- $ A = 1 , B = 0 , X = 2 $
- $ A = 1 , B = 1 , X = 1 $
6. 路径覆盖
覆盖程序中所有可能的路径:
A | B | X | 覆盖路径 |
---|---|---|---|
2 | 0 | 3 | a c e |
1 | 0 | 1 | a b d |
2 | 1 | 1 | a b e |
3 | 0 | 1 | a c d |
黑盒测试
(1) 基本概念
黑盒测试需要根据已知的程序功能和性能,不必了解程序内部结构和处理细节,设计测试用例并通过测试检验程序的每个功能和性能是否正常。
(2) 测试用例设计
1. 等价类划分法
该方法把程序的输入数据集合按输入条件划分为若干个等价类,每一个等价类对于输入条件而言为一组有效或无效的输入,为每一个等价类设计一个测试用例。这样可以减少测试次数,同时不丢失发现错误的机会。每个等价类中的数据具有相同的测试特征。该方法遵循着以下的基本原则:
- 输入条件为一范围,划分出三个等价类:(1) 有效等价类(在范围内),(2) 大于输入最大值,(3)小于输入最少值
- 输入条件为一值,划分为三个等价类:(1) 有效,(2) 大于,(3) 小于
- 输入条件为集合,划分两个等价类:(1) 有效(在集合内),(2) 无效(在集合外)
- 输入条件为一个布尔量,划分两个等价类:(1) 有效(此布尔量),(2)无效(布尔量的非)
例如对于函数 func(x, y)
:
- 当 $0 < x < 1024$ 且 $y = 0$ 时,$z = -1$
- 否则,$z = x \cdot \lg(y)$
关于 $x$ 的等价类
- $(0, 1024)$
- $(-∞, 0]$
- $[1024, +∞)$
关于 $y$ 的等价类
- ${0}$
- $(-∞, 0)$
- $(0, +∞)$
有测试用例
- $X = 1, y = 0, z = -1$
- $X = 1, y = -1, z =**$
- $X = 1, y = 1, z = **$
- $X = 0, y = 1, z = **$
- $X = 0, y = -1, z = **$
- $X = 0, y = 1, z = **$
- $X = 2000, y = 0, z = **$
- $X = 2000, y = -100, z = **$
- $X = 2000, y = 200, z = **$
2. 边界值分析法
该方法遵循着以下的基本原则:
- 若输入条件是一范围(a,b),则使用a,b以及紧挨a,b左右的值应作为测试用例
- 输入条件为一组数,选择这组数最大者和最小者,次大和次小者作为测试用例
- 如果程序的内部数据结构是有界的,应设计测试用例使它能够检查该数据结构的边界
关于 $x$ 的等价类有6个
- -1, 0, 1
- 1023, 1024, 1025
关于 $y$ 的等价类有3个:-1, 0, 1
有测试用例:
- $X = 1, y = 0, z = -1$
- $X = 1, y = -1, z =**$
- $X = 1, y = 1, z = **$
- $X = 0, y = 0, z = **$
- $X = 0, y = -1, z = **$
- $X = 0, y = 1, z = **$
- $X = -1, y = 0, z = **$
- $X = -1, y = -1, z = **$
- $X = -1, y = 1, z = **$
等等。
3. 正交数组测试
当输入参数的数量不多,且每个参数可取的值有明确的界定时使用。例如传真应用系统的send函数参数:P1, P2, P3, P4. 每个参数有3个不同值。如:
- P1 = 1:现在发送
- P1 =2:1H后发送
- P1=3:半夜12点后发送
P2,P3,P4也分别取1,2,3值。该方法的局限是,这些测试参数不相交。检测一个参数值使得软件出故障的逻辑错误(单模式错误),不能查出相互影响。
评审技术
(1) 基本概念
评审是由技术人员对技术人员进行,在软件工程过程中产生的对工作产品的技术评估、软件质量保证机制、训练场地。
(2) 非正式评审
非正式评审包括:与同事就软件工程产品进行的简单桌面检查、以评审一个工作产品为目的的临时会议(涉及两个以上)、结对编程评审。结对编程中有两名角色:
- Driver:控制键盘输入,负责实际的代码编写。根据Navigator的指导,实现具体的编程任务,并确保代码的质量和可读性。
- Navigator:起到领航、提醒和指导的作用。观察Driver的编码过程,提供即时反馈,帮助Driver避免错误,提出改进建议,确保代码符合设计规范和最佳实践。
在结对编程过程中,Navigator会持续审查Driver编写的代码,确保每一行代码都经过仔细检查。这种即时的复审有助于及时发现问题,减少后期调试的时间和成本。而随着大模型(如Codex、CS50等)的出现,结对编程得到了新的助力。这些大模型可以自动生成代码片段、提供建议、解释复杂的编程概念,甚至帮助解决编程难题。
(3) 正时技术评审FTR
FTR实际上是一类评审方式,包括评审会议、走查walkthrough、审查inspection 和轮查Round-robin/pass-round Review。其目标是:
- 发现软件的任何一种表示形式中的功能、逻辑或实现上的错误
- 验证评审中的软件是否满足其需求
- 保证软件的表现符合预先指定的标准
- 获得以统一的方式开发的软件
- 使项目更易于管理
关于评审会议,需要注意,评审会(通常)由3~5人参加(如评审主席、倡导者、生产者、用户代表)。与会人应该提前进行准备,但是占用每人的工作时间应该不超过2小时,且评审会的时间应该少于2小时。FTR关注的是某个工作产品(例如,一部分需求模型、一份详细的构件设计、一个构件的源代码)。
软件质量保证SQA
SQA是贯穿软件过程中每一步的普适性活动。包括,对方法和工具有效应用的规程,对诸如技术评审和软件测试等质量控制活动的监督,变更管理规程,保证符合标准的构成,以及测量和报告机制。统计SQA,负责收集、评估和发布有关软件工程过程的数据,有助于提高产品和软件过程本身的质量。可靠性模型将测量加以扩展,能够收集相关数据推导出失效率和进行可靠性预测。 质量保证的能力标志着工程的程度。
软件维护与演化
何为软件维护
软件维护是指,在软件在交付使用后,由于应用需求和环境变化以及自身问题,对软件系统进行改造和调整的过程。软件需要通过维护来应对下面几种情况:
- 出故障,不可正常工作。潜在的缺陷产生软件错误,需要对这些缺陷进行纠正。
- 服务变化,需要升级。例如软件需求发生了变化,需要增强软件的功能和服务。
- 运行环境变化,需要适应,需要改变软件以在新的环境中运行。
软件维护的形式
(1) 纠正性维护
用户在使用软件过程中一旦发现缺陷,他们会向开发人员提出纠正性维护的请求。纠正性维护需要诊断和改正软件系统中潜藏的缺陷。
(2) 适应性维护
软件运行于一定的环境(硬件、OS、网络等)之上,运行环境发展很快,出现了变化。因此需要进行适应性维护以便适应新的运行环境和平台。
(3) 改善性维护
在软件系统运行期间,用户可能要求增加新的功能、建议修改已有功能或提出其他改进意见。为了满足用户日益增长的各种需求,需要对软件进行改善性维护,增加新的功能、修改已有的功能。
(4) 预防性维护
为进一步改善软件系统的可维护性和可靠性,为以后的软件改进奠定基础的维护活动。需要获取软件结构,重新改善软件结构以便提高软件的可靠性和可维护性等。
软件逻辑老化
软件在维护和演化的过程中出现的用户满意度降低、质量逐渐下降、变更成本不断上升等现象。这些现象发生在逻辑层面,而非发生在物理层面。
软件维护的过程与技术
代码重组
在不改变软件功能的前提下,对程序代码进行重新组织,使得重组后的代码具有更好的可维护性,能够有效支持对代码的变更。
逆向工程
基于低抽象层次软件制品,通过对其进行理解和分析,产生高抽象层次的软件制品
- 通过对程序代码进行逆向的分析,产生与代码相一致的设计模型和文档
- 基于对程序代码和设计模型的理解,逆向分析出软件系统的需求模型和文档
典型应用场景:分析已有程序,寻求比源代码更高层次的抽象形式(如设计甚至需求)
设计重构
如果一个软件的设计文档缺失,软件文档与程序代码不一致、或者软件设计的内容不详实,那么软件维护工程师可以采用设计重构的手段来获得软件设计方面的文档信息。通过读入程序代码,理解和和分析代码中的变量使用、模块内部的封装、模块之间的调用或消息传递、程序的控制路径等方面的信息,产生用自然语言或图形化信息所描述的软件设计文档。设计重构是逆向工程的一种具体表现形式。
再工程
通过分析和变更软件的架构,实现更高质量的软件系统的过程。再工程既包括逆向工程也包括正向工程。
逆向工程、重组、重构和再工程示意图
软件维护成本
软件维护工作量$M = P + K * e^{(c-d)}$,P为生产性工作量,K为经验常数,C为复杂度(设计好坏和文档完整程度),D为对欲维护软件的熟悉程度。维护成本不断增加:70年代 :35%-40%;80年代 :60%;90年代 :75%;如今:数据更高,80%。软件维护工作量涉及二方面:
- 助动性:用于理解代码功能,结构特征以及性能约束
- 生产性:用于分析和评价、修改设计和代码
模型表明,如果没有好的软件开发方法或者软件开发人员不能参与维护,那么软件维护工作量会指数上升。
软件项目管理
软件项目团队的运行模式
模式 | 描述 |
---|---|
一窝蜂模式 | - 无组织,一窝蜂,无序和随意 - 典型例子是小孩子游戏 |
主治医生模式 | - 主治医生主刀,其他人员协助 - 容易产生一人干活,其余打酱油 |
社区模式 | - 志愿者因为兴趣参加,没有报酬,众人拾柴火焰高 - 只烤火不拾柴,柴火质量低 |
功能团队模式 | - 具备不同能力的同事平等协作,共同完成功能 - 一个功能完成之后,这些人又重组织,完成其他功能 - 人员之间没有管理关系,小组内部交流频繁 |
官僚模式 | - 大领导–》小领导–》员工 - 存在明显的领导和管理关系 - 跨组织合作变得困难 |