# software engineering **Repository Path**: ladynine/software-engineering ## Basic Information - **Project Name**: software engineering - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 13 - **Created**: 2022-03-10 - **Last Updated**: 2022-05-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README 文章链接 ~~~bash https://gitee.com/fakerlove/software-engineering ~~~ # 1. 软件与软件工程 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200810090927130.png) ## 1.1软件的概念、特点 > 软件是计算机系统中与硬件相互依存的另一部分,它是包括程序、数据以及相关文档的完整集合。其中,程序是按事先设计的功能和性能要求执行的指令序列;数据是使程序能正常操纵信息的数据结构;文档是与程序开发、维护和使用有关的图文材料。 > > 软件是一种逻辑产品 ### 1.1.1 软件的分类 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20190314133543457.png) ### 1.1.2 软件危机 > 计算机软件的开发和维护过程所遇到的一系列严重问题。 #### 1) 软件危机表现 * 对软件开发成本和进度的估算很不准确 * 难以准确获取用户需求,用户不满意 * 质量很不可靠,没有适当的文档 * 缺乏方法指导和工具支持,大型软件系统经常失败 * 供不应求:软件开发生产率跟不上计算机应用的迅速发展 * 做好软件定义时期的工作,是降低软件成本提高软件质量的关键。 #### 2) 解决途径 * 组织管理 + 工程项目管理方法 * 技术措施 + 软件开发技术与方法 + 软件工具 ## 1.2 软件工程 用工程、科学和数学的原则和方法研制、维护计算机软件的有关技术及管理方法。 ### 1.2.1 三要素 * 方法 * 工具 * 过程 ### 1.2.2 软件工程目标之间的关系 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20190314133619385.png) ### 1.2.3 软件工程的原则 #### 1) 抽象 抽取事物最基本的特性和行为,忽略非基本的细节。采用分层次抽象、自顶向下、逐层分解的办法控制软件开发过程的复杂性。例如:软件瀑布模型、结构化分析方法、结构化设计方法,以及面向对象建模技术等都体现了抽象的原则。 #### 2) 信息隐蔽 将模块设计成“黑箱”,实现的细节隐藏在模块内部,不让模块的使用者直接访问。 #### 3) 模块化 模块是程序在逻辑上相对独立的成分,是独立的编程单位,应有良好的接口定义。模块化有助于信息隐蔽和抽象,有助于表示复杂的系统。 #### 4) 局部化 要求在一个物理模块内集中逻辑上相互关联的计算机资源,保证 模块之间具有松散的耦合,模块内部具有较强的内聚。这有助于加强模块的独立性,控制解的复杂性。 #### 5) 确定性 软件开发过程中所有概念的表达应该是确定的、无歧义的、规范的。这有助于人们之间在交流时不会产生误解、遗漏,保证整个开发工作协调一致。 #### 6) 一致性 整个软件系统(包括程序、文档和数据)的各个模块应使用一致的概念、符号和术语。程序内部接口应保持一致。软件和硬件、操作系统的接口应保持一致。系统规格说明与系统行为应保持一致。用于形式化规格说明的公理系统应保持一致。 #### 7) 完备性 软件系统不丢失任何重要成分,可以完全实现系统所要求功能的程度。为了保证系统的完备性,在软件开发和运行过程中需要严格的技术评审。 #### 8) 可验证性 开发大型的软件系统需要对系统自顶向下、逐层分解。系统分解应遵循系统已于检查、测试、评审的原则,以确保系统的正确性。 ### 1.2.4 软件工程的本质特征 * 软件工程关注大型程序的构造 * 软件工程的中心课题是控制复杂性 * 软件经常变化 * 开发软件的效率非常重要 * 和谐地合作是开发软件的关键 * 软件必须有效地支持它的用户 * 在软件工程领域中是由具有一种文化背景的人替具有另一种文化背景的人 ### 1.2.5 软件工程的基本原理 #### 1) 用分阶段的生命周期计划严格管理 把软件生命周期划分为若干个阶段,并相应地制定出切实可行的计划,然后严格按照计划对软件的开发和维护工作进行管理。 #### 2) 坚持进行阶段评审 软件的质量保证工作不能等到编码阶段结束之后再进行。错误发现与改正越晚,所需付出的代价也越高。因此,在每个阶段都进行严格的评审,以便尽早发现在软件开发过程中所犯的错误,是一条必须遵循的重要原则。 #### 3) 实行严格的产品控制 #### 4) 采用现代程序设计技术 采用先进的技术不仅可以提高软件开发和维护的效率,而且可以提高软件产品的质量。 #### 5) 结果应能清楚地审查 应该根据软件开发项目的总目标及完成期限,规定开发组织的责任和产品标准,从而使得所得到的结果能够清楚地审查。 #### 6) 开发小组的人员应该少而精 ## 1.3 软件工程方法学 > 通常把在软件生命周期全过程中使用的一整套技术方法的集合称为方法学(methodology),也称为范型(paradigm)。 ### 1.3.1 方法学三要素 * 方法,回答“怎么做”的问题 * 工具 * 过程 ### 1.3.2 使用最广泛的软件工程方法学 * 传统方法学→面向数据流、结构化 软件分析→总体设计→详细设计→面向过程的编码→测试 * 面向对象方法学 软件分析与对象抽取→对象详细设计→面向对象的编码→测试 ### 1.3.3 两种程序设计方法 * 结构化程序设计 程序=数据结构+算法 * 面向对象程序设计 程序=对象+消息 ### 1.3.4 软件生命周期-8个任务 >* 计划时期 > + 问题定义 > + 可行性分析 > >* 开发时期 > + 需求分析 > + 软件设计 > + 编码 > + 测试 > >* 运行时期 > + 软件维护 #### 1) 问题定义 问题定义阶段必须回答的关键问题是:“要解决的问题是什么?” 通过对客户的访问调查,系统分析员扼要地写出关于问题性质、工程目标和工程规模的书面报告,经过讨论和必要的修改之后这份报告应该得到客户的确认。 #### 2) 可行性研究 这个阶段回答的关键问题是:“对于上一个阶段所确定的问题有行得通的解决方法吗?” 技术上、经济上、操作上、时间上、法律上 #### 3) 需求分析(功能分析) 这个阶段的任务仍然不是具体解决问题,而是确定“为了解决这个问题,目标系统必须做什么?”,主要是确定目标系统必须具备哪些功能。 系统分析员必须和用户密切配合,充分交流信息,以得出经过用户确认的系统逻辑模型。通常用数据流图(DFD)、数据字典(DD)和简要的算法表示。 在需求分析阶段确定的系统逻辑模型是以后设计和实现系统的基础。这个阶段的一项重要任务是用正式文档准确地记录对目标系统的需求,这份文档通常称为 规格说明书(specification)。(SRS) #### 4) 总体设计 这个阶段必须回答的关键问题是:“怎样实现目标系统?” 成果:系统结构图(SC) 一个程序应该由若干个规模适中的模块按合理的层次结构组织而成。总体设计的另一项主要任务是设计程序的体系结构,也就是确定程序由哪些模块组成以及模块间的关系。 #### 5) 详细设计 这个阶段的任务就是把解法具体化,也就是回答这个关键问题:“应该怎样具体地实现这个系统呢?” 这个阶段的任务还不是编写程序,而是设计出程序的详细规格说明。这种规格说明应该包含必要的细节,程序员可以根据它们写出实际的程序代码。 详细设计也称为模块设计,在这个阶段将详细地设计每个模块,确定实现模块功能所需要的算法和数据结构。 #### 6) 编码和单元测试 这个阶段的关键任务是写出正确的、容易理解、容易维护的程序模块。 程序员应该根据目标系统的性质和实质环境,选择一种适当的高级程序设计语言,把详细设计的结果翻译成用选定的语言书写的程序,并且仔细测试编写出的每一个模块。 #### 7) 综合测试 这个阶段的关键任务是通过各种类型的测试使软件达到预定的要求。 最基本的测试是集成测试和验收测试。 集成测试是根据设计的软件结构,把经过单元测试检验的模块按某种选定的策略装配起来,在装配过程中对程序进行必要的测试。 验收测试是按照规格说明书的规定,由用户对目标系统进行验收。 必要时还可以再通过现场测试或平行运行等方法对目标系统进一步测试检验。 #### 8) 软件维护 这个阶段的关键任务是,通过各种必要的维护活动使系统持久地满足用户的需要。 改正性维护,也就是诊断和改正在使用过程中出现的软件错误; 适应性维护,即修改软件以适应环境的变化 完善性维护,即根据用户的要求改进或扩充软件使它更完善; 预防性维护,即修改软件为将来的维护活动预先做准备。 ### 1.3.5 软件开发模型(过程) 传统开发模型 瀑布模型(waterfall model) 快速原型模型(rapid prototype model) 演化开发模型 增量模型(incremental model) 螺旋模型(spiral model) 面向对象开发模型 构建集成模型(component integration model) 形式化开发模型 转换模型(transformational model) 净室模型(clean room model) #### 1) 瀑布模型 瀑布模型是文档驱动的。 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20190314133648847.png) 瀑布模型的特点: * 阶段间具有顺序性和依赖性 * 推迟实现的观点 瀑布模型在编码之前设置了系统分析与系统设计的各个阶段,清楚地区分逻辑设计与物理设计,尽可能推迟程序的物理实现,是按照瀑布模型开发软件的一条重要指导思想。 * 质量保证的观点 * 每个阶段都必须完成规定的文档,没有交出合格的文档就是没有完成该阶段的任务。 * 每个阶段结束前都要对所完成的文档进行评审,以便尽早发现问题,改正错误。 #### 2) 快速原型模型 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20190314133703938.png) 快速原型模型适用于中小型软件,且需求模糊的用户。 **快速原型模型的特点**: * 快速开发工具 * 循环 * 低成本 **种类**: * 渐进型 * 抛弃型 #### 3) 增量模型 使用增量模型开发软件时,把软件产品作为一系列的增量构件来设计、编码、集成和测试。每个构件由多个相互作用的模块构成,并且能够完成特定的功能。 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20190314133744980.png) **增量:**小而可用的软件 增量模型的特点: 在前面增量的基础上开发后面的增量 每个增量的开发可用瀑布或快速原型模型 迭代的思路 #### 4) 螺旋模型 螺旋模型是风险驱动的。 螺旋模型的基本思想是,使用原型及其他方法来尽量降低风险。理解这种模型的一个简便方法,是把它看作在每个阶段之前都增加了风险分析过程的快速原型模型。 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/2019031413375523.png) **螺旋模型的特点**: * 瀑布模型+快速原型+风险分析 * 迭代过程 **一个螺旋式周期**: 确定目标、选择方案、选定完成目标的策略 风险角度分析该策略 启动一个开发阶段 评价前一步的结果,计划下一轮的工作 #### 5) 构建集成模型 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20190314133807813.png) 构建集成模型的特点: * 面向对象 * 基于构件库 * 融合螺旋模型特征 * 支持软件开发的迭代方法 * 软件重用 # 2. 可行性研究 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/2020081010253775.png) ## 2.1 可行性研究的任务 ### 2.1.1 目的和任务 > 可行性研究的目的不是解决问题,而是**确定问题是否值得去解决。** > > 可行性研究最根本的任务是对以后的行动方针提出建议。 ### 2.1.2 本质 可行性研究的本质是要进行一次大大压缩简化了的系统分析和设计的过程,也就是在较高层次上以较抽象的方式进行的系统分析和设计的过程。 ### 2.1.3 步骤 * 首先需要进一步分析和澄清问题定义 * 在澄清了问题定义之后,分析员应该导出系统的逻辑模型。 * 再从逻辑模型出发,探索若干种可供选择的主要解法(即系统实现方案)。对每种解法都应该仔细研究它的可行性,一般说来,至少应该从下述三个方面研究每种解法的可行性: + 技术可行性:使用现有的技术能够实现这个系统 + 经济可行性:这个系统的经济效益能超过它的开发成本? + 操作可行性:系统的操作方式在这个用户组织内行得通吗 + 必要时还应该从法律、社会效益等更广泛的方面研究每种解法的可行性。 ### 2.1.4 投入时间 可行性研究需要的时间长短取决于工程的规模。一般来说,可行性研究的成本只是预期的工程总成本的5%~10%。 ## 2.2 可行性研究过程 典型的可行性研究过程有下述一些步骤: ### 2.2.1 复查系统规模和目标 分析员对问题定义阶段书写的关于规模和目标的报告书进一步复查确认,改正含糊或不确切的叙述,清晰地描述对目标系统的一切限制和约束 ### 2.2.2 研究目前正在使用的系统 现有的系统是信息的重要来源,新的目标系统必须也能完成它的基本功能 现有的系统必然有某些缺点,新系统必须能解决旧系统中存在的问题 运行使用旧系统所需要的费用是一个重要的经济指标,如果新系统不能增加收入或减少使用费用,那么从经济角度看新系统就不如旧系统。 ### 2.2.3 导出新系统的高层逻辑模型 优秀的设计过程通常总是从现有的物理系统出发,导出现有系统的逻辑模型,再参考现有系统的逻辑模型,设想目标系统的逻辑模型,最后根据目标系统的逻辑模型建造新的物理系统。 通过前一步的工作,分析员能够使用数据流图,描绘数据在系统中流动和处理的情况,从而概括的表达出对新系统的设想。为了把系统描绘的更清洗准确,还应该有一个初步的数据字典,定义系统中使用的数据。 ### 2.2.4 进一步定义问题 新系统的逻辑模型实质上表达了分析员对新系统必须做什么的看法。分析员应该和用户一起再次复查问题定义、工程规模和目标,这次复查应该把数据流图和数据字典作为讨论的基础 前4个步骤构成一个循环,直到提出的逻辑模型完全符合系统目标。 ### 2.2.5 导出和评价供选择的解法 分析员应该从他建议的系统逻辑模型出发,导出若干个较高层次的物理解法供比较和选择。 导出供选择的解法的最简单的途径,是从技术角度出发考虑解决问题的不同方案。 首先,根据技术可行性的考虑初步排除一些不现实的系统方案 其次,考虑操作方面的可行性。分析员应该根据使用部门处理事务的原则和习惯检查技术上可行的哪些方案。 最后考虑经济方面的可行性。分析员应该估计余下的每个可能的系统的开发成本和运行费用,并且估计相对于现有的系统而言这个系统可以节省的开支或可以增加的收入。 ### 2.2.6 推荐行动方针 根据可行性研究结果应该做出的一个关键性决定是,是否继续进行这项开发工程 如果分析员认为值得继续进行这项开发工程,那么他应该选择一种最好的解法,并且说明选择这个解决方案的理由 通常使用部门的负责人主要根据经济上是否划算决定是否投资于一项开发工程,因此分析员对于所推荐的系统必须进行比较仔细的成本/效益分析。 ### 2.2.7 草拟开发计划 分析员应该为所推荐的方案草拟一份开发计划,除了制定工程进度表之外还应该估计对各类开发人员和各种资源的需要情况,应该指明什么时候使用以及使用多长时间。 此外,还应该估计系统生命周期每个阶段的成本。 最后,应该给出下一个阶段(需求分析)的详细进度表和成本估计。 ## 2.3 系统流程图 > 系统流程图是概括地描绘物理系统的传统工具。它的基本思想是用图形符号以黑盒子形式描绘组成系统的每个组件(程序、文档、数据库、人工过程等) > > 系统流程图表达的是数据在系统各部件之间流动的情况,而不是对数据进行加工处理的控制过程,因此尽管系统流程图的某些符号和程序流程图的符号形式相同,但是它却是物理数据流图而不是程序流程图。 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20190314133836123.png) ## 2.4 数据流图 DFD > 数据流图是一种图形化技术,它描绘信息流和数据从输入移动到输出的过程中所经受的变换(加工、处理)。 > > **它是以图示的方式来表示软件模型** * 在数据流图中没有任何具体的物理部件,它只是描绘数据在软件中流动和被处理的逻辑过程。 * 数据流图是系统逻辑功能的图形表示,即使不是专业的计算机技术人员也容易理解它,因此是分析员与用户之间极好的通信工具 * 设计数据流程图时只需考虑系统必须完成的基本逻辑功能,完全不需要考虑怎样具体地实现这些功能。 ### 2.4.1 顶级数据流图(0级) ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20190314133846128.png) ### 2.4.2 符号 四种基本符号: * 正方形或立方体表示数据的源点或终点 * 圆角矩形或圆形代表变换数据的处理 * 开口矩形或两条平行横线代表数据存储 * 箭头表示数据流,即特定数据的流动方向 #### 例子 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20190314133905343.png) 第一步从问题描述中提取数 据流图的4种成份: * 首先考虑数据的源点和终点。 * 再次考虑处理。 * 最后,考虑数据流和数据存储。系统把订货报表送到采购部,因此订货报表是一个数据流;事务需要从仓库送到系统中,显然事务是另一个数据流。产生报表和处理事务在时间上明显不匹配,所以每当有一个事务发生时立即处理它,然而每天只产生一次订货报表。因此, ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20190314133938219.png) ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/2019031413392351.png) ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20190314134027186.png) ### 2.4.3 分层数据流图 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20190314134041240.png) ## 2.5 数据字典 DD > 数据字典是关于数据信息的集合,是数据流图中所有元素严格定义的集合 ### 2.5.1 定义 > 数据字典有以下四类条目:数据项(数据流分量)、数据流、文件(数据存储)、基本加工(处理)。 1.数据流 :要定义数据流图中的数据流就要用数据流条目。数据流条目给出了某个数据流的定义,通常是列出该数据流的各个组成数据项。 订货单(数据流条目)=配件号(数据项)+配件名+规格+数量+顾客名+地址; ### 2.5.2 定义方法 > 数据元素组成数据的方式:顺序,选择,重复,可选。 数据流条目的描述内容: (1)名称:数据流名。 (2)别名:数据流的另一个名字。 (3)简述:对数据流的简单描述和说明。 (4)组成:描述数据流由哪些数据项组成,使用如表3-1所示的描述符号来表示数据流的组成 2.数据项条目 : 数据流的组成成员是数据项,数据项条目是不可再分解的数据单位,是组成数据流和数据存储的最小元素。数据项条目的描述内容如下: (1)名称:数据项名。 (2)别名:数据项的另一个名字 (3)简述:对数据项的简单描述 (9)注解:对数据项的其它补充说明。 ## 2.6 成本/效益分析 ### 2.6.1 成本估计 ## 2.7 结构化分析方法SA ### 简介 > 结构化分析(Structured Analysis,简称SA 法)是**面向数据流的需求分析方法**,是70年代由Yourdon,Constaintine 及DeMarco 等人提出和发展,并得到广泛的应用。 > > 结构化分析方法的基本思想是**“分解”和“抽象”**。 分解:是指对于一个复杂的系统,为了将复杂性降低到可以掌握的程度,可以把大问题分解成若干小问题,然后分别解决。 图4 是自顶向下逐层分解的示意图。顶层抽象地描述了整个系统,底层具体地画出了系统的每一个细节,而中间层是从抽象到具体的逐层过渡。 抽象:分解可以分层进行,即先考虑问题最本质的属性,暂把细节略去,以后再逐层添加细节,直至涉及到最详细的内容,这种用最本质的属性表示一个自系统的方法就是“抽象”。 ![clip_image001](https://gitee.com/fakerlove/picture_1/raw/master/o_clip_image001_thumb.gif) ### 结构化分析所使用的工具 - 数据流图(Data Flow Diagram,DFD) - 数据字典(DataDictionary,DD) - 结构化语言 - 判定表 - 判定树 ### 数据流图 ![img](https://gitee.com/fakerlove/picture_1/raw/master/20210227223437807.PNG) ## 2.8 结构化设计方法(SD) ### 2.8.1 概念 > 结构化分析方法(Structured Method,结构化方法)是一种软件开发方法,一般利用图形表达用户需求,强调开发方法的结构合理性以及所开发软件的结构合理性。 > > 结构化设计方法(SD)和结构化分析方法(SA)一样遵循_抽象思维模式,采用逐步求精技术,SD通常与SA联系,即依据数据流程图设计程序的结构。 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200426091819290.png) ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200426092210731.png) ### 2.8.2 步骤 #### 1) 概要设计与详细设计 ##### 概要设计 * 概要设计阶段的**主要任务**是设计软件的结构、确定系统是由哪些模块组成,以及每个模块之间的关系。 * 它采用结构图(包括模块、调用、数据)来描述程序的结构,此外还可以使用层次图和HIPO(层次图加输入/处理/输出图)。 * 整个过程主要包括 + 复查基本系统模型 + 复查并精化数据流图 + 确定数据流图的信息流类型(包括交换流和事务流) + 根据流类型分别实施变换分析或事务分析 + 根据软件设计原则对得到的软件结构图进一步优化。 * **概要设计的结果是提供一份模块设计书** ##### 详细设计 * 而详细设计阶段的主要任务则是**确定应该如何具体地实现所要求的系统**,得出对目标系统的精确描述。它采用自顶向下、逐步求精的设计方式和单入口单出口的控制结构。 * **常使用的工具**包括程序流程图、盒图、PAD(Problem Analysis Diagram,问题分析图)、PDL(Program Design Language,程序设计语言)。 #### 2) 结构图 如图 8-9 所示,结构图的基本成分包括模块、调用(模块之间的调用关系)和数据(模块间传递及处理数据信息)。 ![img](https://gitee.com/fakerlove/picture_1/raw/master/88edb731-3c8b-4443-9bb1-51991ff68a25.png) 结构图是在需求分析阶段产生的数据流图的基础上进行进一步的设计。它将 DFD 图中的信息流分为两种类型。 变换流:信息首先沿着输入通路进入系统,并将其转换为内部表示,然后通过变换中心(加工)的处理,再沿着输出转换为外部形式离开系统。具有这种特性的加工流就是变换流。 事务流:信息首先沿着输入通路进入系统,事务中心根据输入信息的类型在若干个动作序列(活动流)中选择一个执行,这种信息流称为事务流。 #### 3) 软件需求分析 程序流程图和盒图都是用来描述程序的细节逻辑的,其符号如图 8-10 所示。 程序流程图的特点是简单、直观、易学,但它的缺点也正是由于其随意性而使得画出来的流程图容易成为非结构化的流程图。而盒图正是为了解决这一问题设计的,它是一种符合结构化程序设计原则的图形描述工具。 盒图的主要特点是功能域明确、无法任意转移控制、容易确定全局数据和局部数据的作用域、容易表示嵌套关系、可以表示模块的层次结构。但它也带来了一个副作用,那就是修改相对比较困难。 ![img](https://gitee.com/fakerlove/picture_1/raw/master/71b0f9e7-63df-433a-9d1e-b0f0e1a7a738.png) #### 4) PAD 和 PDL PAD 是问题分析图的缩写,它符合自顶向下、逐步求精的原则,也符合结构化程序设计的思想,它最大的特点在于能够很方便地转换为程序语言的源程序代码。 PDL 则是语言描述工具的缩写,它和高级程序语言很相似,也包括数据说明部分和过程部分,还可以带注解等成分,但它是不可执行的。 PDL 是一种形式化语言,其控制结构的描述是确定的,但内部的描述语法是不确定的。PDL 通常也被称为伪码。 # 3. 需求分析 ## 3.1 需求分析 ### 3.1.1 需求分析的概念 > 需求分析是软件定义时期的最后一个阶段,它的基本任务是准确地回答“系统必须做什么?”这个问题,对目标系统提出完整、准确、清晰、具体的要求。 > 系统分析员应该写出**软件需求规格说明书**,以书面形式准确地描述软件需求。 ### 3.1.2 不同层次下的软件需求 #### 功能性需求 * 业务需求(Business Requirements):客户对于系统的高层次目标要求,定义了项目的远景和范畴。 * 用户需求(User Requirements):从用户角度描述的系统功能需求与非功能需求,通常只涉及系统的外部行为而不涉及内部特性。 * 系统需求(System Requirements, SR):系统应该提供的功能或服务,通常涉及用户或外部系统与该系统之间的交互,不考虑系统内部的实现细节。 #### 非功能性需求 (质量、性能) 描述了不直接关联到系统功能行为的系统的方方面面。从各个角度对系统进行约束和限制,反映了客户对软件系统质量和性能的额外要求,如可靠性、可用性、性能、可支持性等。详细如响应时间、数据精度、可靠性等。 ## 3.2 需求分析的任务 ### 3.2.1 确定对系统的综合要求 > 1.功能需求 > 2.性能需求 > 3.可靠性和可用性需求 > 4.出错处理需求 > 5.接口需求 > 6.约束 > 7.逆向需求 > 8.将来可能提出的要求 ### 3.2.2 分析系统的数据要求 > 建立数据模型 ——ER图。 > 描绘数据结构—— 层次方框图和 Warnier 图。 > 数据结构规范化。 ### 3.2.3 导出系统的逻辑模型 根据前两项分析结果导出详细的逻辑模型,通常用 数据流图、实体- 联系图(ER图)、状态转换图 、 数据字典和主要的处理算法描述这个逻辑模型。 ### 3.2.4 修正系统开发计划 分析建模与规格说明 模型:对事物的一种无歧义的书面描述 。 ## 3.3 分析建模(重要) ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200725111302545.png) ### 软件需求规格说明 > 是需求分析阶段得出的最主要的文档。 ## 3.4 实体-联系图 概念性数据模型反映了用户的现实环境,且与软件系统中的实现方法无关。 数据模型包含三种互相关联的信息: > 数据对象,矩形框表示 > 数据对象的属性,菱形框表示 > 数据对象彼此之间相互连接的关系,圆角矩阵或椭圆形表示。 ## 3.5 状态转换图(重点) 通过描绘系统的状态及引起系统状态转换的事件,来表示系统的行为。状态图还指明了作为特定事件的结果系统将做哪些动作(例如,处理数据)。 ### 3.5.1 状态 是任何可以被观察到的系统行为模式,一个状态代表系统的一种行为模式。状态规定了系统对事件的响应方式。 初态:用实心圆表示,只能有 1 个; 终态:用一对同心圆( 内圆为实心圆) 表示,可能有 0 ~ 多个; 中间状态:用圆角矩形表示,分成上、中、下3 部分。 > 上面部分----- 为状态的名称; > 中间部分----- 为状态变量的名字和值; > 下面部分----- 是活动表。 状态转换:带箭头的连线,箭头指明转换方向。 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200725112739545.png) ### 3.5.2 活动 #### 活动表 格式:事件名(参数表)/动作表达式** “事件名”可以是任何事件的名称。 常用的3种标准事件: 1、entry事件指定进入该状态的动作; 2、exit事件指定退出该状态的动作; 3、do事件则指定在该状态下的动作。 需要时可以为事件指定参数表。活动表中的动作表达式描述应做的具体动作。 #### 事件表达式 格式:事件说明[守卫条件]/动作表达式 事件说明格式:事件名(参数表)。 守卫条件是一个过程表达式。 ### 3.5.3 实例 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200725113255542.png) # 4. 总体设计 (概要设计) ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200810133702654.png) > **总体设计的基本目的就是回答“概括地说,系统应该如何实现”这个问题**,因此,总体设计又称为概要设计或初步设计。 ## 4.1 设计过程 典型的总体设计过程包括9个步骤: (1)设想供选择的方案 (2)选取合理方案(通常至少选取三种方案:低成本、中等成本和高成本) (3)推荐最佳方案 (4)功能分解 (5)设计软件结构 (6)设计数据库 (7)制定测试计划 (8)书写文档 (9)审查和复审 ## 4.2 设计原理 ### 4.2.1 模块化 模块化就是把系统划分为多个独立命名且可独立访问的模块,每个模块完成一个子功能,把这些模块集成起来,可以完成用户指定的所有功能。 ### 4.2.2 逐步求精 为了能集中精力解决主要问题而尽量推迟对问题细节的考虑。 ### 4.2.3 信息隐藏和局部化 > 信息隐藏:一个模块内包含的过程和数据,对于不需要这些信息的其他模块,是不可见,不可访问的。 > 局部化:把一些关系密切的软件元素物理地放得彼此靠近。 > > **在软件结构设计时,必须遵循信息隐蔽** ### 4.2.4 模块独立 独立的模块容易开发、维护和测试。 模块的独立程度有两个标准度量:内聚和耦合 #### 1) 耦合 耦合是对于不同模块间互联程度的度量 耦合分为 * 数据耦合 两个模块通过参数交换数据。数据耦合属于低耦合,系统中必须存在。 * 控制耦合 传递的信息有控制信息。属于中等耦合。 * 特征耦合 把整个数据结构作为参数传递,而调用模块只需数据结构中一部分。 * 公共环境耦合 多个模块通过一个公共数据环境相互作用。 公共环境可以是全局变量、共享的通信区等。 * 内容耦合 + 一个模块访问另一个模块的内部数据 + 一个模块不通过正常入口进入另一模块的内部 + 两模块程序代码重叠 + 一个模块有多个入口 > **内容耦合**是最高程度耦合,应坚决避免使用内容耦合。 > 耦合原则:尽量使用数据耦合,少用控制耦合和特征耦合,限制公共环境耦合范围,完全不用内容耦合。 #### 2) 内聚 **分类** > ①偶然内聚:一个模块完成一组任务,任务之间即使有关系,关系也是很松散的。 > ②逻辑内聚:一个模块完成的任务在逻辑上属于相同或相似的一类。 > ③时间内聚:一个模块包含的任务必须在同一段时间内执行。 > ④过程内聚:一个模块内的处理元素是相关的,而且必须以特定次序执行。 > ⑤通信内聚:模块中所有元素都使用同一个输入数据和产生同一个输出数据。 > ⑥顺序内聚:一个模块内的处理元素和同一个功能密切相关,而且处理必须顺序执行。 > ⑦功能内聚:模块内所有处理元素属于一个整体,完成一个单一的功能。 内聚衡量一个模块内部各元素彼此结合的紧密程度。 内聚分为: (1)低内聚 (2)中内聚 (3)高内聚 ### 4.2.5 抽象 现实世界中一定事物、状态或过程之间总存在着某些相似的方面(共性)。把这些相似的方面集中和概括起来,暂时忽略它们之间的差异,这就是抽象。**抽象就是抽出事物本质特性而暂时不考虑细节。** ## 4.3 启发规则 1.改进软件结构提高模块独立性: 通过模块分解或合并,降低耦合提高内聚 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200810141832838.png) 2.模块规模应该适中 3.深度、宽度、扇入和扇出都应适当: (1)深度:软件结构中控制的层数,它往往能粗略地标志一个系统的大小和复杂程度。 (2)宽度:软件结构内同一个层次上的模块总数的最大值。 (3)扇出:一个模块直接控制(调用)的模块数目。 (4)扇入:有多少个上级模块直接调用它。 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200810142054809.png) ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/2020081014211270.png) 4.模块的作用域应该在控制域之内: (1)模块的作用域:定义为受该模块内一个判定影响的所有模块的集合。 (2)模块的控制域:是这个模块本身以及所有直接或间接从属于它的模块的集合。 在一个设计得很好的系统中,所有受判定影响的模块应该都从属于做出判定的那个模块,最好局限于做出判定的那个模块本身及它的直属下级模块。 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200810142426976.png) 5.力争降低模块接口的复杂程度 6.设计单入口单出口的模块 7.模块功能应该可以预测 ## 4.4 面向数据流的设计方法 ### 4.4.1 概念 面向数据流的设计方法把信息流映射成软件结构,信息流的类型决定了映射的方法。 ### 4.4.2 信息流的类型 #### (1)交换流 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200810143135977.png) #### (2)事务流 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200810143144617.png) ### 4.4.3 变换分析 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200810143511925.png) ### 4.4.4 事务分析 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200810143518231.png) 例题 > 练习题: > 1.耦合是对软件不同模块之间互连程度的度量。各种耦合从强到弱的排列为( )。 > A、内容耦合,控制耦合,数据耦合,公共环境耦合 > B、内容耦合,控制耦合,公共环境耦合,数据耦合 > C、内容耦合,公共环境耦合,控制耦合,数据耦合 > D、控制耦合,内容耦合,数据耦合,公共环境耦合 > > 2.软件结构图的形态特征能反映程序重用率的是( )。 > A、深度 > B、宽度 > C、扇入 > D、扇出 > > 3.概要设计的目的是确定整个系统的( )。 > A、规模 > B、功能及模块结构 > C、费用 > D、测试方案 > > 4.数据耦合和控制耦合相比,则( )成立。 > A、数据耦合的耦合性强 > B、控制耦合的耦合性强 > C、两者的耦合性相当 > D、两者的耦合性需要根据具体情况分析 > > 5.当一个模块直接使用另一个模块的内部数据时,这种模块之间的耦合为( )。 > A、数据耦合 > B、公共耦合 > C、标记耦合 > D、内容耦合 > > 6.如果在需求分析阶段采用了结构化分析方法,则软件设计阶段就应采用结构化设计方法。() > 7.概要设计与详细设计之间的关系是全局和局部的关系。() > > 选择题答案:CCBBD > 判断答案:对对 > > 8.什么是软件的概要设计?概要设计阶段完成的主要任务是什么? > 总体设计又称概要设计,是将软件需求转化为软件体系结构、确定系统级接口、全局数据结构和数据库模式。 # 5. 详细设计(模块设计) > 一般包括结构化程序设计,面向对象设计方法,面向数据结构设计方法(Jackson) ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200810143740990.png) ## 5.1 结构程序设计 ### 定义 > * 结构化程序设计采用**自顶向下、逐步求精**的设计方法,各个模块通过“顺序、选择、循环”的控制结构进行连接,并且只有一个入口、一个出口。\ > * 结构化程序设计的原则可表示为:程序=(算法)+(数据结构)。 > * 如果一个程序的代码块仅仅通过**顺序、选择和循环**这3 种基本控制结构进行连接,并且**每个代码块只有一个入口和一个出口,则称这个程序是结构化的。** 下图中(a)为顺序结构,(b)为 IF-THEN-ELSE选择(分支)结构,(c)为 DO-WHILE 型循环结构。 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200727150921790.png) 图(a)为 DO - UNTIL型循环结构,(b)多分支结构。 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200727150900118.png) ## 5.2 人机界面设计 人机界面设计是接口设计的重要组成部分。对于交互式系统来说,人机界面设计和数据设计、体系结构设计及过程设计一样重要。(可参考概要设计)。 ### 5.2.1 设计问题 设计人机界面过程中会遇到的4 个问题:系统响应时间、用户帮助设施、出错信息处理、命令交互。 #### 1) 系统响应时间 作出控制动作到系统响应的时间。具有长度和易变性。 #### 2) 用户帮助设施 包括集成的帮助设施(设计在软件里面)和附加的帮助设施(联机用户手册)。前者优于后者。 #### 3) 出错信息处理 出错信息和警告信息。 #### 4) 命令交互 通常即可从菜单栏选择,又可以通过键盘命令调用。 ### 5.2.2 设计过程 是一个迭代的过程。 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200727150625631.png) #### 人机界面设计指南 包括一般交互指南、信息显示指南、数据输入指南。 ## 5.3 详细设计工具 | 工具名 | 主要优点 | 主要缺点 | | --------------- | ------------------------------------------ | ---------------------------- | | 程序流程图 | 简单直观 | 不考虑全局;不易表示数据结构 | | 盒图(N-S图) | 考虑全局;作用域明确 | 不易绘制和修改 | | PAD图 | 清晰表现逻辑结构、数据结构;有直接转换工具 | ~ | | 判定表 | 清晰表现动作关系 | 含义复杂;不够简洁 | | 判定树 | 形式简单 | 简洁性比判定表还要差 | | 过程设计语言PDL | 便于保持文档和程序一致性;便于数据结构说明 | 不直观 | ### 5.3.1 程序流程图 又称为程序框图,**历史最悠久、使用最广泛**的方法。 > 优点: > 对控制流程的描绘很直观,便于初学者掌握。 > > 缺点: > 1、不够逐步求精,程序员只考虑程序的控制流程,而不考虑全局结构。 > 2、用箭头代表控制流,因此程序员不受任何约束,可以完全不顾结构程序设计的精神,随意转移控制。 > 3、不易表示数据结构。 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200731152732690.png) ### 5.3.2 盒图(N-S图) > 优点 : > 1.功能域(作用域)明确 > 2.无 GOTO > 3.容易确定局部或全局数据的作用域 > 4.容易表示嵌套关系,体现层次结构 > > 缺点 : > 1.修改困难,特别在手工画时 > 2.分支或嵌套多时不好画 ### 5.3.3 PAD图 PAD是问题分析图(problem analysis diagram)的英文缩写,用**二维树形结构的图来表示程序的控制流,将这种图翻译成程序代码比较容易。** ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200731153629930.png) >优点: > >* 使用表示结构化控制结构的PAD符号设计出来的程序必然是结构化程序。 >* 描绘的程序结构十分清晰。 >* 程序逻辑易读、易懂、易记。  >* 容易将PAD图转换成高级语言源程序,可用软件工具自动完成。 >* 即可表示程序逻辑,也可描绘数据结构。 >* 支持自顶向下、逐步求精方法的使用。 >* 能够详细地表现程序的层次结构 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200731153641537.png) ### 5.3.4 判定表 当算法中包含多重嵌套的条件选择时,判定表(树)能够清晰地表示复杂的条件组合与应做的动作之间的对应关系。 其他方法都不可以。 一张判定表由4部分组成: ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200731153721714.png) > 优点: > 能清晰地表示复杂的条件组合与应做的动作之间的对应关系。 > > 缺点: > 1.含义无法一眼看出,需要有一个简短的学习过程。 > 2.当数据元素的值多于两个时,判定表的简洁程度也将下降。 ### 5.3.5 判定树 判定树是判定表的变种,也能清晰地表示复杂的条件组合与应做的动作之间的对应关系。多年来判定树一直受到人们的重视,是一种比较常用的系统分析和设计的工具。 > 优点: > 它的形式简单,一眼就可以看出其含义,因此易于掌握和使用。 > > 缺点: > 简洁性不如判定表,数据元素的同一个值往往要重复写多遍,而且越接近树的叶端重复次数越多。 画判定树时分枝的次序可能对最终画出的判定树的简洁程度有较大影响。 例题 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200731154148512.png) 利用判定表: ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200731154211669.png) 利用判定树: ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200731154237633.png) ### 5.3.6 过程设计语言(PDL) 也称为伪码,它是用正文形式表示数据和处理过程的设计工具。 PDL具有严格的关键字外部语法,用于定义控制结构和数据结构;同时,PDL表示实际操作和条件的内部语法通常又是灵活自由的,可以适应各种工程项目的需要。它使用一种语言的词汇,同时却使用另一种语言的语法。 伪代码的基本控制结构: * 简单陈述句结构:避免复合语句。 * 判定结构:IF_THEN_ELSE或CASE_OF结构。 * 选择结构:WHILE_DO或REPEAT_UNTIL结构。 > 特点: > > * 关键字的固定语法,它提供了结构化控制结构、数据说明和模块化的特点。 > * 自然语言的自由语法,它描述处理特点。 > * 数据说明的手段。应该既包括简单的数据结构,又包括复杂的数据结构。 > * 模块定义和调用的技术,应该提供各种接口描述模式。 > 优点: > > * 可以作为注释直接插在源程序中间。有助于保持文档和程序的一致性,提高了文档的质量。 > * 可以使用普通的正文编辑程序或文字处理系统,很方便地完成PDL的书写和编辑工作。 > * 已经有自动处理程序存在,而且可以自动由PDL生成程序代码。 > > 缺点: > 不如图形工具形象直观,描述复杂的条件组合与动作间的对应关系时,不如判定表清晰简单。 ## 5.4 面向数据结构设计方法 ### 5.4.1 JackSon 图 ### 5.4.2 改进的Jackson 图 ### 5.4.3 Jackson 方法 ## 5.5 程序复杂程度的定量度量 # 6. 编码 ## 6.1 选择程序设计语言 程序设计语言的特点会影响人的思维和解题方式,会影响人和计算机通信的方式和质量,也会影响其他人阅读和理解程序的难易程度。 编码之前的选择一种适当的程序设计语言是一项重要工作。 高级语言明显优于汇编语言,在需要效率的某些场合,需要汇编语言,或者大型系统中执行时间非常关键的(或直接依赖于硬件的)一小部分代码需要用汇编语言书写之外,其他程序应该一律用高级语言书写。 有理想的模块化机制,以及可读性好的控制结构和数据结构;为了便于调试和提高软件可靠性,语言特点应该使编译程序能够尽可能多地发现程序中的错误;为了降低软件开发和维护的成本,选用的高级语言应该有良好的独立编译机制。 选择语言的主要**实现标准**: * (1) 系统用户的要求。如果所开发的系统由用户负责维护,用户通常要求用他们熟悉的语言书写程序。 * (2) 可以使用的编译程序。运行目标系统的环境中可以提供的编译程序往往限制了可以选用的语言的范围。 * (3) 可以得到的软件工具。如果某种语言有支持程序开发的软件工具可以利用,则目标系统的实现和验证都变得比较容易。 * (4) 工程规模。如果工程规模很庞大,现有的语言又不完全适用,那么设计并实现一种供这个工程项目专用的程序设计语言,可能是一个正确的选择。 * (5) 程序员的知识。虽然对于有经验的程序员来说,学习一种新语言并不困难,但是要完全掌握一种新语言却需要实践。如果和其他标准不矛盾,那么应该选择一种已经为程序员所熟悉的语言。 * (6) 软件可移植性要求。如果目标系统将在几台不同的计算机上运行,或者预期的使用寿命很长,那么选择一种标准化程度高、程序可移植性好的语言就是很重要的。 * (7) 软件的应用领域。所谓的通用程序设计语言实际上并不是对所有应用领域都同样适用。因此,选择语言时应该充分考虑目标系统的应用范围。 ## 6.2 编码风格 源程序代码的逻辑简明清晰、易读易懂应该遵循下述规则: ### 1.程序内部的文档 包括恰当的标识符、适当的注解和程序的视觉组织等等。   选取含义鲜明的名字,使它能正确地提示程序对象所代表的实体,这对于帮助阅读者理解程序是很重要的。如果使用缩写,那么缩写规则应该一致,并且应该给每个名字加注解。   注解非常有助于对程序的理解。   每个模块开始处有序言性的注解:简要描述模块的功能、主要算法、接口特点、重要数据以及开发简史;   程序中间与一段程序代码有关的注解:主要解释包含这段代码的必要性。   不能滥用注释,应利用注解提供一些额外的信息。注解的内容一定要正确。   程序清单的布局对于程序的可读性也有很大影响,应该利用适当的阶梯形式使程序的层次结构清晰明显。 ### 2.数据说明 数据说明的次序应该标准化。有次序就容易查阅,因此能够加速测试、调试和维护的过程。   当多个变量名在一个语句中说明时,应该按字母顺序排列这些变量。   如果设计时使用了一个复杂的数据结构,则应该用注解说明用程序设计语言实现这个数据结构的方法和特点。 ### 3. 语句构造 构造语句时应该遵循的原则是,每个语句都应该简单而直接,不能为了提高效率而使程序变得过分复杂。下述规则有助于使语句简单明了: 不要为了节省空间而把多个语句写在同一行; - 尽量避免复杂的条件测试; 尽量减少对“非”条件的测试; 避免大量使用循环嵌套和条件嵌套; 利用括号使逻辑表达式或算术表达式的运算次序清晰直观。 ### 4. 输入输出 在设计和编写程序时应该考虑下述有关输入输出风格的规则: - 对所有输入数据都进行检验; - 检查输入项重要组合的合法性; - 保持输入格式简单; - 使用数据结束标记,不要要求用户指定数据的数目; - 明确提示交互式输入的请求,详细说明可用的选择或边界数值; - 当程序设计语言对格式有严格要求时,应保持输入格式一致; - 设计良好的输出报表; - 给所有输出数据加标志。 ### 5. 效率 效率主要指处理机时间和存储器容量两个方面。 应该清晰3条概念: 首先,效率是性能要求,因此应该在需求分析阶段确定效率方面的要求。软件应该像对它要求的那样有效,而不应该如同人类可能做到的那样有效(需求分析相关); 其次,效率是靠好设计来提高的(设计相关); 第三,程序的效率和程序的简单程度是一致的,不要牺牲程序的清晰性和可读性来不必要地提高效率(效率不是第一位的)。下面从三个方面进一步讨论效率问题。 - (1)程序运行时间 - (2)存储器效率 - (3) 输入输出的效率 # 7. 综合测试 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200813202755205.png) ## 7.1 软件测试 ### 7.1.1 (软件)测试的定义或目标 (1)测试是为了发现程序中的错误而执行程序的过程 (2)好的测试方案极可能是发现了迄今为止尚未发现的错误的测试方案 (3)成功的测试是发现了至今为止尚未发现的错误的测试 ### 7.1.2 测试方法 (1)黑盒测试(功能测试) 把程序看作一个黑盒子;完全不考虑程序的内部结构和处理过程;是在程序接口进行的测试 (2)白盒测试(结构测试) 把程序看成装在一个透明的白色盒子里;测试者完全知道程序的结构和处理算法;按照程序内部的逻辑测试程序,检测程序中的主要执行通路是否能按预定要求工作 ### 7.1.3 测试步骤 模块测试(单元测试)、子系统测试、系统测试、验收测试、平行运行 ## 7.2 白盒测试技术 1.测试用例 测试用例=测试数据+预期的结果 2.逻辑覆盖 (1)语句覆盖 每个语句至少执行一次 (2)判定覆盖 每个语句至少执行一次,每个判定的每个分支至少执行一次 (3)条件覆盖 每个语句至少执行一次,判定表达式的每种结果至少执行一次 条件覆盖通常比判定覆盖强,有时条件覆盖也是判定覆盖 (4)判定/条件覆盖 覆盖判定+条件判定 有时判定/条件覆盖也不一定比条件覆盖强 ## 7.3 黑盒测试技术 ### 7.3.1 基本概念 (1)黑盒测试着重测试软件功能 (2)黑盒测试不能取代白盒测试,两者为互补关系 (3)白盒测试—测试过程的早期;黑盒测试——测试过程的后期 #### 7.3.2 黑盒测试准则 (1)测试用例尽可能少 (2)一个测试用例能指出一类错误 ### 7.3.3 黑盒测试手段 #### 等价划分 #### 边界值分析 #### 错误推测 ## 7.4 单元测试 1.单元测试集中检测模块 2.单元测试和编码属于软件过程的同一个阶段 3.可以应用人工测试和计算机测试这样两种不同类型的测试方法 4.单元测试主要使用白盒测试技术,对多个模块的测试可以并行地进行 ## 7.5 集成测试 1.集成测试是测试和组装软件的系统化技术,主要目标是发现与接口有关的问题 2.集成测试的策略 (1)非渐增式 (2)自顶向下 (3)自底向上 (4)混合 3.回归测试:重新执行已经做过的测试的某个子集,保证程序的变化没有带来非预期的副作用 ## 7.6 确认测试 确认测试也称为验收测试,它的目标是验证软件的有效性。   那么,什么样的软件才是有效的呢?软件有效性的一个简单定义是: 如果软件的功能和性能如同用户所合理期待的那样,软件就是有效的。   需求分析阶段产生的软件需求规格说明书,准确地描述了用户对软件的合理期望,因此是软件有效性的标准,也是进行确认测试的基础。   确认测试必须有用户积极参与,或者以用户为主进行。 ### 7.6.1 确认测试的范围 确认测试通常使用黑盒测试法。   应该仔细设计测试计划和测试过程,保证软件能满足所有功能要求,能达到每个性能要求,文档资料是准确而完整的,此外,还应该保证软件能满足如安全性、可移植性、兼容性和可维护性等的要求。 ### 7.6.2 软件配置复查 确认测试的一个重要内容是复查软件配置。   复查的目的是保证软件配置的所有成分都齐全,质量符合要求,文档与程序完全一致,具有完成软件维护所必须的细节,而且已经编好目录。 ### 7.6.3 Alpha和Beta测试 Alpha测试由用户在开发者的场所进行,并且在开发者对用户的“指导”下进行测试。开发者负责记录发现的错误和使用中遇到的问题。   总之,Alpha测试是在受控的环境中进行的。   Beta测试由软件的最终用户们在一个或多个客户场所进行,开发者通常不在Beta测试的现场。   用户记录在Beta测试过程中遇到的问题,并且定期把这些问题报告给开发者。   接收到在Beta测试期间报告的问题之后,开发者对软件产品进行必要的修改,并准备向全体客户发布最终的软件产品。 ## 7.7 调试 1.调试是在测试发现错误之后排除错误的过程 2.调试的方法 蛮干法、回溯法、原因排除法 ## 7.8 软件可靠性 1.软件可靠性 程序在给定的时间间隔内,按照规格说明书的规定成功地运行的概率 2.估算平均无故障时间的方法 (1)计算平均无故障时间/需要改正多少个错误测试结束 MTTF=1/K(ET/IT-Ec(t)/It),即Ec=ET-IT/KxMTTF MTTF:平均无故障时间 K:常数,典型值200 IT:程序长度(机器指令总数) ET:测试之前程序中的错误总数 Ec:在时间段内发现的错误总数 (2)估计错误总数的方法 B0=B1B2/bc B0:t=0时错误总数 B1:t=t1时测试员甲发现的错误数 B2:t=t1时测试员乙发现的错误数 bc:t=t1时两个测试员发现的相同错误数 例题 > 练习题: > 1.测试用例是专门为了发现软件错误而设计的一组或多组数据,它由( )组成。 > A、测试输入数据 > B、预期的测试输出数据 > C、测试输入与预期的输出数据 > D、按照测试用例设计方法设计出的数据 > > 2.一个成功的测试是( )。 > A、发现错误 > B、发现至今尚未发现的错误 > C、没有发现错误 > D、证明发现不了错误 > > 3.单元测试阶段主要涉及( )的文档。 > A、需求设计 > B、编码和详细设计 > C、详细设计 > D、概要设计 > > 4.软件调试的目的是( )。 > A、发现错误 > B、改正错误 > C、改善软件的性能 > D、挖掘软件的潜能 > > 选择题答案:CBBB > > 5.测试是为了验证该软件以正确地实现了用户的需求。() > 6.白盒测试法是根据程序的功能来设计测试用例的。() > 7.确定测试计划是在需求分析阶段制定的。() > 8.单元测试是在编码阶段完成的。() > > 判断题答案:错错对对 > > 9.在测试一个长度为24000条指令的程序时,第一个月由甲、乙两名测试员各自独立测试这个程序。经一个月测 > 试后,甲发现并改正20个错误,使MTTF达到10h。与此同时,乙发现24个错误,其中6个甲也发现了。以后由甲 > 一个人继续测试这个程序。问: (1) 刚开始测试时程序中总共有多少个潜藏的错误? (2) 为使MTTF达到60h, > 必须再改正多少个错误? > 解: > (1)B0=B1B2/bc -> B0=20x24/6=80(个) > (2)E=B0-I/KxMTTF -> K=I/MTTFx(B0-E)=24000/10x(80-20)=40 > E'=B0-I/KxMTTF'=80-24000/40x60=70(个) 还需再改正70-20=50个错误 > > 10.为以下程序段设计一组测试用例,要求分别满足语句覆盖、判定覆盖、条件覆盖。 > > int test(int A, int B) > { > if ((A > 1) AND(B < 10)) > then X = A - B; > > if ((A = 2) OR(B > 20)) > then X = A + B; > > return x; > } > > 解:(建议画出流程图方便解题) > 语句覆盖:A=2,B=1,X=3 > 判定覆盖:A=3,B=1,X=2 > A=2,B=10,X=12 > 条件覆盖:A=2,B=1,X=3 > A=0,B=21,X=21 > (答案不唯一) # 8. 软件维护 ## 8.1 软件维护的类型 软件维护是指软件系统交付使用以后,为了改正错误或满足新的需求而修改软件的过程。按照不同的维护目的,维护工作可分成4类。 ### 一、软件维护的类型 - 完善性维护(Perfective Maintenance) 扩充原有系统的功能,提高原有系统的性能,满足用户的实际需要。 - 纠错性维护(Corrective Maintenance) 对在测试阶段未能发现的,在软件投入使用后才逐渐暴露出来的错误的测试、诊断、定位、纠错以及验证、修改的回归测试过程。 - 适应性维护(Adaptive Maintenance) 要使运行的软件能适应运行环境的变动而修改软件的过程。 - 预防性维护(Preventive Maintenance) 为了进一步改善软件的可靠性和易维护性,或者为将来的维护奠定更好的基础而对软件进行修改。 四类软件维护的比例 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20191030120428982.png) ### 二、维护的步骤 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20191030120441856.png) ### 三、维护工作的组织管理 ```bash 软件维护工作不仅是技术性的,它还需要大量的管理工作与之相配合,才能保证维护工作的质量。管理部门应对提交的修改方案进行分析和审查,并对修改带来的影响作充分的估计,对于不妥的修改予以撤销。需修改主文档时,管理部门更应仔细审查。 ``` ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20191030120827317.png) ## 8.2 软件维护的特性 ### 一、结构化维护与非结构化维护 - 结构化维护 — 指软件开发过程是按照软件工程方法,软件的维护过程,有一整套完整的方案、技术、审定过程。 - 非结构化维护 — 缺乏必要的文档说明,难于确定数据结构、系统接口等特性。维护工作令人生畏,事倍功半。 ### 二、软件维护的代价 维护费用高达开发费用的55% — 70%,而且逐年上涨。   维护中还可能引入新的潜在错误。 Belady 和 Lehman 提出软件维护工作模型:     M=P+K*e(C – D) 其中:     M—维护总工作量     P—生产性活动 K—经验常数     C—程序复杂度(由非结构化维护引起的)     D—对维护软件熟悉程度的度量。 ## 8.3 软件维护的技术 ### 一、面向维护的技术 在软件开发阶段用来减少错误,提高软件可维护性的技术。涉及到软件开发的所有阶段。   可维护性(可测试性、可理解性、可修改性、可移植性、可重用性) ### 二、软件支援技术 在软件维护阶段用于提高维护工作的效率和质量的技术。主要用到测试阶段的技术。 ### 三、软件维护中应注意的问题(谨慎、工具使用) # 9. 面向对象方法学引论 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200810152736566.png) > 面向对象的思想最初出现于挪威斯陆大学和挪威计算机中心共同研制的Simula 67 语言中,其后,随着位于美国加利福尼亚的Xerox(施乐) 研究中心推出的Smalltalk–76 和80 语言,面向对象的的程序设计技术迅猛的发展。 >   到了20世纪90年代,面向对象方法学已经成为人们在开发软件是首选的成熟的范型,成为当前最好的软件开发技术。 >   传统的软件工程方法是面向过程的,将数据和处理过程分离,求解过程是先对应用领域(问题空间)进行分析,建立起问题空间的逻辑模型,再通过一系列复杂的转换和算法,构造计算机系统,获得解空间。 >   由于问题空间与解空间的模型、描述方式的不同,存在着复杂的转换过程,需求变化就更难适应。传统的软件工程方法难于支持软件复用。 ## 9.1 面向对象方法概述 ### 9.1.1 什么是面向对象的开发方法 OOSD(Object-Oriented Software Development)法是一种把面向对象的思想应用于软件开发过程,指导开发活动的系统方法。 面向对象的方法是一种运用对象、类、继承、封装、聚合、消息传送、多态性等概念来构造系统的软件开发方法。 什么是面向对象   根据Coad和Yourdon的定义,按照以下4个概念设计和实现的系统,称为是面向对象的。 面向对象: 对象 (object) 类 (classification)       继承(inheritance)       通信 (communication with messages)   面向对象技术的特点    1、对软件开发过程所有阶段进行综合考虑。  2、软件生存期各阶段所使用的方法、技术具有高度的连续性,用符合人类认识世界的思维方式来分析、解决问题。    3、将OOA、OOD、OOP有机地集成在一起。       OOA(Object-Oriented Analysis)面向对象分析      OOD( Object-Oriented Design )面向对象设计      OOP( Object-Oriented Program )面向对象的程序设计 ### 9.1.2 面向对象开发方法的组成 #### OOSD由三部分组成 * OOA(Object-Oriented Analysis)面向对象的分析 强调的是对一个系统中的对象特征和行为的定义。建立系统的三类(对象、状态、处理)模型。 * OOD(Object-Oriented Design)面向对象的设计 与OOA密切配合顺序实现对现实世界的进一步建模。 * OOP (Object-Oriented Program)面向对象的程序设计 是面向对象的技术中发展最快的,使用面向对象的程序设计语言,进行编码。 #### 1) OOA法 就是要解决“做什么”的问题。OOA 法的基本任务就是要建立三种模型: ##### 对象模型(信息模型) 定义构成系统的类和对象,它们的属性与操作。 ##### 状态模型(动态模型) * 描述任何时刻对象的联系及其联系的改变,即时序。常用状态图, 事件追踪图描述。 * 功能模型(函数模型) * 描述系统内部数据的传送处理。 显然,在三大模型中,最重要的是对象模型。 #### 2) OOD 法 在需求分析的基础上,进一步解决“如何作”的问题,OOD 法也分为概要设计和详细设计。 概要设计:细化对象行为,添加新对象,认定类,组类库,确定外部接口及主要数据结构 详细设计:加细对象描述 #### 3) OOP 法 使用面向对象的程序设计语言,如C++进行程序设计。   Coad和Yourdon给出一个面向对象的定义:   面向对象=对象+类+继承+消息   如果一个软件系统是按照这样四个概念设计和实现的,则可以认为这个软件系统是面向对象的。 ## 9.2 面向对象的概念 理解面向对象的基本概念对于学习和掌握面向对象的开发方法是十分重要的。 ### 9.2.1 对象 > 对象(Object)是客观事物或概念的抽象表述,即对客观存在的事物的描述统称为对象,对象可以是事、物、或抽象概念 ,是将一组数据和使用该数据的一组基本操作或过程封装在一起的实体。    * 对象都存在一定的状态(state),内部标识(identity),可以给对象定义一组运算(operation) * 对象通过其运算所展示的特定行为称为对象行为(behavior), * 对象本身的性质称为属性(attribute), * 对象将它自身的属性及运算“包装起来”,称为“封装”(encapsulation). * 对象的最基本的特征是**封装性和继承性。** ### 9.2.2 其它概念 #### 1) 类(Class) > 类又称对象类(Object Class),是一组具有相同属性和相同操作的对象的集合。在一个类中,每个对象都是类的实例(instance) ,它们都可以使用类中提供的函数。 类具有属性,用数据结构来描述类的属性,类具有操作,它是对象的行为的抽象,操作实现的过程称为方法(method) ,方法有方法名,方法体和参数。 由于对象是类的实例,在进行分析和设计时,通常把注意力集中在类上,而不是具体的对象上。 #### 2) 继承 (Inheritance) > 继承是使用现存的定义作为基础,建立新定义的技术。是父类和子类之间共享数据结构和方法的机制,这是类之间的一种关系。在定义和实现一个类的时候,可以在一个已经存在的类的基础上来进行,把这个已经存在的类所定义的内容做为自己的内容,并加入若干新内容。 > 继承性分类: >   单重继承:一个子类只有一个父类。即子类只继承一个父类 的数据结构和方法。 >   多重继承:一个子类可有多个父类。继承多个父类的数据结 构和方法。 #### 3) 消息(Message) > 消息就是向对象发出的服务请求(互相联系、协同工作等)。对象之间的联系可表示为对象间的消息传递,即对象间的通讯机制。 一个消息应该包含以下信息:消息名、接收消息对象的标识、服务标识 、消息和方法、输入信息、回答信息。   在对象的操作中当一个消息发送给某个对象时,消息包含接收对象去执行某种操作的消息。 注意:在并发系统中,多个控制线程(Thread of Control)并发执行,情况就复杂得多,消息可以是发出服务请求、提交数据、发布事件信息、或是传递同步控制信息。 #### 4.) 多态性和动态绑定 多态性(Polymorphism)是指相同的操作或函数,过程作用于不同的对象上并获得不同的结果。 即相同的操作的消息发送给不同的对象时,每个对象将根据自己所属类中所定义的操作去执行,故产生不同的结果。 例如: “绘图”操作,作用在“椭圆” 和“矩形” 上,画出不同的图形。 动态绑定(dynamic binding)是在运行时根据对象接收的消息动态地确定要连接的服务代码。 #### 5) 方法(method) > 类中操作的实现过程称为方法。 一个方法包括方法名、参数及方法体。 方法描述了类与对象的行为,每一个对象都封装了数据和算法两个方面,数据由一组属性表示,而算法即是当一个对象接收到一条消息后,它所包含的方法决定对象如何动作。通常是在某种编程语言(如Java、C++)下实施的运算。 ## 9.3 面向对象建模 - 用面向对象方法成功地开发软件的关键,同样是对问题域的理解。面向对象方法最基本的原则,是按照人们习惯的思维方式,用面向对象观点建立问题域的模型,开发出尽可能自然地表现求解方法的软件。 - 用面向对象方法开发软件,通常需要建立3种形式的模型,它们分别是描述系统数据结构的对象模型,描述系统控制结构的动态模型和描述系统功能的功能模型。 ## 9.4 对象模型 面向对象方法强调围绕对象而不是围绕功能来构造系统。对象模型为建立动态模型和功能模型,提供了实质性的框架。 统一建模语言UML; - 定义类(属性、服务)、类的图形符号; - 类与类之间的关联、泛化(继承)、依赖和细化等关系及其表示等等。 ## 9.5 动态模型 动态模型表示瞬时的、行为化的系统的“控制”性质,它规定了对象模型中的对象的合法变化序列。 * 对一个对象来说,生命周期由许多阶段组成,在每个特定阶段中,都有适合该对象的一组运行规律和行为规则,用以规范该对象的行为。生命周期中的阶段也就是对象的状态。 * 各对象之间相互触发就形成了一系列的状态变化。我们把一个触发行为称作一个事件。 * 状态与事件密不可分,一个事件分开两个状态,一个状态隔开两个事件。事件表示时刻,状态代表时间间隔。 ## 9.6 功能模型 功能模型表示变化的系统的“功能”性质,它指明了系统应该“做什么”,因此更直接地反映了用户对目标系统的需求。 1. 功能模型由一组数据流图组成。在面向对象方法学中,数据流图远不如在结构分析、设计方法中那样重要。 2. 同对象模型和动态模型比较,数据流图并没有增加新的信息,但是,建立功能模型有助于软件开发人员更深入地理解问题域,改进和完善自己的设计。 3. 因此,不能完全忽视功能模型的作用。 ## 9.7 3种模型之间的关系 对象模型、动态模型、功能模型分别从3个不同侧面描述了所要开发的系统。   这3种模型相互补充、相互配合,使得我们对系统的认识更加全面:   功能模型指明了系统应该“做什么”;   动态模型明确规定了什么时候(即在何种状态下接受了什么事件的触发)做;   对象模型则定义了做事情的实体。 其中,对象模型是最基本最重要的,它为其他两种模型奠定了基础,我们依靠对象模型完成3种模型的集成。  (1)针对每个类建立的动态模型,描述了类实例的生命周期或运行周期。  (2)状态转换驱使行为发生,这些行为在数据流图中被映射成处理,在用例图中被映射成用例,它们同时与类图中的服务相对应。  (3)功能模型中的处理(或用例)对应于对象模型中的类所提供的服务。  (4)数据流图中的数据存储,以及数据的源点/终点,通常是对象模型中的对象。  (5)数据流图中的数据流,往往是对象模型中对象的属性值,也可能是整个对象。  (6)用例图中的行为者,可能是对象模型中的对象。  (7)功能模型中的处理(或用例)可能产生动态模型中的事件。  (8)对象模型描述了数据流图中的数据流、数据存储以及数据源点/终点的结构。 # 10. 面向对象分析 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/2020081016000386.png) **面向对象=对象+类+继承+消息通信** ## 10.1 面向对象的基本过程 ### 10.1.1 概述 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20191030124316715.png) 面向对象开发过程的应用生存期模型 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20191030124346967.png)                           OOA分析过程 ### 10.1.2 3个子模型与5个层次 面向对象建模得到系统3子模型: * 静态结构——对象模型 * 交互次序——动态模型 * 数据变换——功能模型 任何问题,都需要从客观世界实体及实体间相互关系抽象出极有价值的对象模型; 当问题涉及交互作用和时序时,动态模型是重要的; 解决运算量很大的问题,则涉及重要的功能模型。 动态模型和功能模型中都包含了对象模型中的操作(即服务或方法)。复杂问题的对象模型通常由5个层次组成,这5个层次,一层比一层显现出对象模型的更多细节。在概念上,这5个层次是整个模型的5张水平切片。 5个层次对应着在面向对象分析过程中建立对象模型的5项主要活动: * 找出类与对象 * 识别结构 * 识别主题 * 定义属性 * 定义服务 “5项活动”不是5个步骤,事实上,这5项工作完全没有必要顺序完成,也无须彻底完成一项工作以后再开始另外一项工作。 在概念上可以认为,面向对象分析大体上按照下列顺序进行: * 寻找类与对象,识别结构,识别主题,定义属性,建立动态模型,建立功能模型,定义服务。 * 但是,分析不可能严格地按照预定顺序进行,大型、复杂系统的模型需要反复构造多遍才能建成。 * 通常,先构造出模型的子集,然后再逐渐扩充,直到完全、充分地理解了整个问题,才能最终把模型建立起来。 ## 10.2 需求陈述 ### 10.2.1 书写要点 需求陈述的内容包括:问题范围,功能需求,性能需求,应用环境及假设条件等。 需求陈述应该阐明“做什么”,指出哪些是系统必要的性质,哪些是任选的性质。 不能指望没有经过全面、深入分析的需求陈述是完整、准确、有效的。随后进行的面向对象分析的目的,就是全面深入地理解问题域和用户的真实需求,建立起问题域的精确模型。 ### 10.2.2 例子 自动取款机(ATM)系统,如图所示。 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20191030124438660.png) 某银行拟开发一个自动取款机系统,它是一个由自动取款机、中央计算机、分行计算机及柜员终端组成的网络系统。ATM和中央计算机由总行投资购买。总行拥有多台ATM,分别设在全市各主要街道上。分行负责提供分行计算机和柜员终端。柜员终端设在分行营业厅及分行下属的各个储蓄所内。该系统的软件开发成本由各个分行分摊。   银行柜员使用柜员终端处理储户提交的储蓄事务。储户可以用现金或支票向自己拥有的某个账户内存款或开新账户。储户也可以从自己的账户中取款。通常,一个储户可能拥有多个账户。柜员负责把储户提交的存款或取款事务输进柜员终端,接收储户交来的现金或支票,或付给储户现金。柜员终端与相应的分行计算机通信,分行计算机具体处理针对某个账户的事务并且维护账户。   拥有银行账户的储户有权申请领取现金兑换卡。 使用现金兑换卡可以通过ATM访问自己的账户。目前仅限于用现金兑换卡在ATM上提取现金(即取款),或查询有关自己账户的信息(例如,某个指定账户上的余额)。将来可能还要求使用ATM办理转账、存款等事务。 所谓现金兑换卡就是一张特制的磁卡,上面有分行代码和卡号。分行代码惟一标识总行下属的一个分行,卡号确定了这张卡可以访问哪些账户。   通常,一张卡可以访问储户的若干个账户,但是不一定能访问这个储户的全部账户。每张现金兑换卡仅属于一个储户所有,但是,同一张卡可能有多个副本,因此,必须考虑同时在若干台ATM 上使用同样的现金兑换卡的可能性。也就是说,系统应该能够处理并发的访问。 当用户把现金兑换卡插入ATM之后,ATM就与用户交互,以获取有关这次事务的信息,并与中央计算机交换关于事务的信息。   首先,ATM要求用户输入密码,接下来ATM把从这张卡上读到的信息以及用户输入的密码传给中央计算机,请求中央计算机核对这些信息并处理这次事务。中央计算机根据卡上的分行代码确定这次事务与分行的对应关系,并且委托相应的分行计算机验证用户密码。如果用户输入的密码是正确的,ATM就要求用户选择事务类型(取款、查询等)。当用户选择取款时,ATM请求用户输入取款额。最后,ATM从现金出口吐出现金,并且打印出账单交给用户。 ## 10.3 建立对象模型 用面向对象方法开发绝大多数软件时,首先建立对象模型,然后再建立另外两个子模型。 * 对象模型(静态数据结构)对应用细节依赖较少,比较容易确定;当用户的需求变化时,静态数据结构相对来说比较稳定——优先创建的理由。 * 建立对象模型时的主要信息来源:需求陈述、应用领域的专业知识以及关于客观世界的常识。 对象模型通常有5个层次,有一定的开发步骤和规律:   首先确定对象类和关联①,对于大型复杂问题还要进一步划分出若干个主题②;   其次为类和关联增添属性③,进一步描述它们;利用适当的继承④关系进一步合并和组织类;   暂时不确定类中操作⑤(服务,建立动态模型和功能模型之后,更准确地描述了对类中提供的服务的需求)。   面向对象的分析过程,是一个需要反复迭代、逐步深化、逐步完善的过程。 ### 10.3.1 确定类与对象 类与对象在问题域中客观存在的,需要通过分析找出这些类与对象,具体做法: #### 1) 找出候选的类与对象 对象是对问题域中有意义的事物的抽象,大多数客观事物可分为下述5类:   (1) 可感知的物理实体;例如:飞机、汽车、书、房屋等等。   (2) 人或组织的角色;例如:医生、教师、雇主、雇员、计算机系、财务处等等。   (3) 应该记忆的事件;例如:飞行、演出、访问、交通事故等等。   (4) 两个或多个对象的相互作用,通常具有交易或接触的性质;例如:购买、纳税、结婚等等。   (5) 需要说明的概念;例如:政策、保险政策、版权法等等。 可参照上述5类常见事物,找出候选类与对象。 另一种是非正式分析(更简单)。   这种分析方法以用自然语言书写的需求陈述为依据,把陈述中的名词作为类与对象的候选者,用形容词作为确定属性的线索,把动词作为服务(操作)的候选者。   当然,这种方法选取的候选者往往不准确,须经更进一步的严格筛选。      以ATM系统为例,说明非正式分析过程。 #### 2) 找出所有名词: 银行,自动取款机(ATM),系统,中央计算机,分行计算机,柜员终端,网络,总行,分行,软件,成本,市,街道,营业厅,储蓄所,柜员,储户,现金,支票,账户,事务,现金兑换卡,余额,磁卡,分行代码,卡号,用户,副本,信息,密码,类型,取款额,账单,访问。 根据领域知识或常识进一步把隐含的类与对象提取出来:“通信链路”和“事务日志”。 #### 3) 筛选出正确的类与对象 严格考察每个候选对象,去掉不正确的或不必要的,仅保留确实应该记录其信息或需要其提供服务的那些对象。 筛选时主要依据下列标准,删除不正确或不必要的类与对象: ##### (1) 冗余 如果两个类表达了同样的信息,则应该保留在此问题域中最富于描述力的名称。   用非正式分析法得出了34个候选的类。  其中储户与用户、现金兑换卡与磁卡及副本,分别描述了相同的两类信息,因此,应该去掉“用户”、“磁卡”、“副本”等冗余的类,仅保留“储户”和“现金兑换卡”这两个类。 ##### (2) 无关 许多对象与系统无关,应该把它们删掉。   在ATM系统中,不处理分摊软件开发成本问题,并且ATM和柜员终端放置的地点与本系统关系不大。   因此,应该去掉候选类“成本”、“市”、“街道”、“营业厅”和“储蓄所”。 ##### (3) 笼统 通常把笼统的或模糊的类去掉。   在ATM系统中,“银行”实际指总行或分行, “访问”在这里实际指事务,“信息”的具体内容在需求陈述中随后就指明了。此外还有一些笼统含糊的名词。总之,在本例中应该去掉“银行”、“网络”、“系统”、“软件”、“信息”、“访问”等候选类。 ##### (4) 属性 在需求陈述中有些名词实际上描述的是其他对象的属性,应该把这些名词从候选类与对象中去掉。   在ATM系统的例子中,“现金”、“支票”、“取款额”、“账单”、“余额”、“分行代码”、“卡号”、“密码”、“类型”等,实际上都应该作为属性对待。 ##### (5) 操作 在需求陈述中一些既可作为名词,又可作为动词的词,应确定作为类还是作为类中定义的操作。   例如,谈到电话时“拨号”通常当作动词,当构造电话模型时确实应该把它作为一个操作,而不是一个类。但在开发电话的自动记账系统时,“拨号”需要有自己的属性(日期、时间、受话地点等),应该把它作为一个类。   本身具有属性需独立存在的操作,应该作为类与对象。 ##### (6) 实现 应该去掉仅仅和实现有关的候选的类与对象(分析阶段不要过早考虑实现,分散注意力)。   在ATM系统的例子中,“事务日志”是对一系列事务的记录,它是面向对象设计的议题;“通信链路”在逻辑上是一种联系,在系统实现时它是关联类的物理实现。 应该暂时去掉“事务日志”和“通信链路”这两个类,在设计或实现时再考虑它们。 综上所述,在ATM系统的例子中,经过初步筛选,剩下下列类与对象:ATM、中央计算机、分行计算机、柜员终端、总行、分行、柜员、储户、账户、事务、现金兑换卡。(11个类对象) ### 10.3.2 确定关联 两个或多个对象之间的相互依赖、相互作用的关系就是关联。   确定关联,能促使分析员考虑问题域的边缘情况,有助于发现那些尚未被发现的类与对象。 #### 1) 初步确定关联 在需求陈述中使用的描述性动词或动词词组,通常表示关联关系,大多数关联可以通过直接提取需求陈述中的动词词组而得出。 进一步分析需求陈述,还能发现陈述中隐含的关联。另外与用户及领域专家对问题域实体间的相互依赖、作用关系,再进一步补充一些关联。 以ATM系统为例,经分析初步确定出下列关联: ##### (1) 直接提取动词短语得出的关联 ATM、中央计算机、分行计算机及柜员终端组成网络; 总行拥有多台ATM; ATM设在主要街道上; 分行提供分行计算机和柜员终端; 柜员终端设在分行营业厅及储蓄所内; 分行分摊软件开发成本;储户拥有账户; 分行计算机处理针对账户的事务; 分行计算机维护账户。 柜员终端与分行计算机通信;柜员输入针对账户的事务; ATM与中央计算机交换关于事务的信息;中央计算机确定事务与分行的对应关系; ATM读现金兑换卡; ATM与用户交互; ATM吐出现金; ATM打印账单; 系统处理并发的访问。 ##### (2) 需求陈述中隐含的关联总行由各个分行组成; 分行保管账户;总行拥有中央计算机;系统维护事务日志;系统提供必要的安全性;储户拥有现金兑换卡。 ##### (3) 根据问题域知识得出的关联现金兑换卡访问账户;分行雇用柜员。 #### 2) 筛选 根据下述标准删除候选的关联: ##### (1) 已删去的类之间的关联 删除那些已被删除的候选类相关的关联。 已删除的候选类:系统、网络、市、街道、成本、软件、事务日志、现金、营业厅、储蓄所、账单等;与这些类有关的下列8个关联也应该删去: ①ATM、中央计算机、分行计算机及柜员终端组成网络; ②ATM设在主要街道上; ③分行分摊软件开发成本; ④系统提供必要的安全性; ⑤系统维护事务日志; ⑥ATM吐出现金; ⑦TM打印账单; ⑧柜员终端设在分行营业厅及储蓄所内。 ##### (2) 与问题无关的或应在实现阶段考虑的关联应该把处在本问题域之外的关联或与实现密切相关的关联删去。 例如:例子中“系统处理并发的访问”,并发事件没有设计对象间的新关联(没有提供新的关联),它只是需要在实现阶段实现并发访问的算法,以处理并发事务。 ##### (3) 瞬时事件 关联应该描述问题域的静态结构,而不应该是一个瞬时事件。   例如:“ATM读现金兑换卡”描述了ATM与用户交互周期中的一个动作,并不是ATM与现金兑换卡之间的固有关系→删去。 类似,删去“ATM与用户交互”候选的关联。 ##### (4) 三元关联 一般将三个或三个以上对象间的关联,分解为二元关联或用词组描述成限定的关联。 在ATM系统中,“柜员输入针对账户的事务” 可分解成“柜员输入事务”和“事务修改账户” 这样两个二元关联。 “分行计算机处理针对账户的事务”同上分解。 “ATM与中央计算机交换关于事务的信息”候选关联,隐含了“ATM与中央计算机通信”和“在ATM上输入事务”这两个二元关联。 ##### (5) 派生关联 去掉可以用其他关联定义的冗余关联。 在ATM系统的例子中: “总行拥有多台ATM ”实质上是“总行拥有中央计算机”和“ATM与中央计算机通信”这两个关联组合的结果; “分行计算机维护账户”的实际含义是“分行保管账户”和“事务修改账户”。 #### 4) 进一步完善 从几个方面进一步完善经筛选后余下的关联: ##### (1) 正名 选择含义更明确的名字作为关联名。 “分行提供分行计算机和柜员终端”改为: “分行拥有分行计算机” “分行拥有柜员终端”。 ##### (2) 分解 分解以前确定的类与对象,以适用不同的关联。 在ATM系统中,应把“事务”分解成“远程事务”和“柜员事务”(12个类与对象)。 ##### (3) 补充 发现了遗漏的关联及时补上。 在ATM系统中,把“事务”分解成上述两类之后,需要补充“柜员输入柜员事务”、“柜员事务输进柜员终端”、“在ATM上输入远程事务” 和“远程事务由现金兑换卡授权”等关联。 ##### (4) 标明重数 初步判定各关联类型,并粗略确定关联的重数,伴随着对问题认识的逐渐深入,重数也会改动。 12个类与对象:ATM、中央计算机、分行计算机、柜员终端、总行、分行、柜员、储户、账户、“远程事务”、“柜员事务”、现金兑换卡。  各类对象之间的关联: 总行拥有中央计算机(1:1); ATM与中央计算机通信(n:1);总行由各分行组成(1:n); 分行拥有分行计算机(1:1);分行拥有柜员终端(1:n);储户拥有账户(1:n);柜员终端与分行计算机通信(n:1) ;柜员输入柜员事务(1:n); 分行保管账户(1:n); 柜员事务修改账户(1:n); 柜员终端输入柜员事务(1:n);中央计算机与分行计算机通信(1:n) ; ATM输入远程事务(1:n);远程事务修改帐户(n:1) ; 现金兑换卡授权远程事务(1:n) ;储户拥有现金兑换卡(1:n) ;现金兑换卡访问账户(n:n) ;分行雇用柜员(1:n) ; 下图是经上述分析过程之后得出的ATM系统原始的类图: ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20191030125319279.png) ### 10.3.3 划分主题 为了降低复杂度,将一个大的系统再进一步划分成几个不同的主题。 当系统对象较多(系统规模较大),可参照如下进行:先识别出类与对象和关联,然后划分主题,并用它作为指导开发者和用户观察整个模型的一种机制; 对于规模极大的系统,则首先由高级分析员粗略地识别对象和关联,然后初步划分主题,经进一步分析,对系统结构有更深入的了解之后,再进一步修改和精炼主题。 划分主体的原则: 应按问题领域而不是用功能分解方法来确定主题。另外,各个主题内对象应较少依赖和交互。 以ATM系统为例,可以把它划分成总行(包含总行和中央计算机这两个类)、分行(包含分行、分行计算机、柜员终端、柜员事务、柜员和账户等类)和ATM(包含ATM、远程事务、现金兑换卡和储户等类)等3个主题。 教材中ATM系统是一个简化的系统,将不考虑主题层。 ### 10.3.4 确定属性 属性是对象的性质,藉助于属性我们能对类与对象和结构有更深入更具体的认识。注意,在分析阶段不要用属性来表示对象间的关系,使用关联能够表示两个对象间的任何关系,而且把关系表示得更清晰、更醒目。 一般说来,确定属性的过程包括分析和选择两个步骤。 属性是对象的性质,确定属性有助于我们对类与对象和结构有更深入更具体的认识。 确定属性的过程包括分析和选择两个步骤。 #### 1) 分析 一般在需求陈述中用名词词组表示属性,例如 “汽车的颜色”或“光标的位置”。 往往用形容词表示可枚举的具体属性,例如, “红色的”、“打开的”。 此外还需要领域知识和常识才能分析得出需要的属性。 由于属性对问题域的基本结构影响很小。结构相对稳定,属性可能随时间的推移而改变了。 在分析中首先找出最重要的属性,再逐渐把其余属性增添进去。 #### 2) 选择 删掉不正确的或不必要的属性: ##### (1) 误把对象当作属性 如果某个实体的独立存在比它的值更重要,则应把它作为一个对象而不是对象的属性。同一个实体在不同应用领域中可扮演 ##### (2)误把关联类的属性当作一般对象的属性 如果某个性质依赖于某个关联链的存在,则该性质是关联类的属性,在分析阶段不应该把它作为一般对象的属性。 如:学生选课程(n:m),成绩是关联类属性。 ##### (3)把限定误当成属性 在ATM系统的例子中,“分行代码”、“账号”、“雇员号”等都是限定词。 使用关系限定符可以 - 更精确地表达实体关系 - 直接指导实现代码的编写 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20191030130455779.png) 新的领域知识:每年要有一套价格表 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20191030130524372.png) UML图表示 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20191030130556560.png) 代码 产品实体类可以使用一个类型为IDictionary的属性关联价格实体类 ~~~java public class 产品 { private IDictionary _价格s; public IDictionary 价格s { get { return _价格s; } set { _价格s = value; } } } ~~~ 产品 AK47 = new 产品Finder().Find产品ByName(“AK47”); decimal price = AK47.价格s[2008].标准价格; 或者为价格Finder添加一个 FindBy产品Id年度() 函数 产品 AK47 = new 产品Finder().FindByName(“AK47”); 价格 AK47价格 = new 价格Finder().FindBy产品Id年度(AK47.产品 Id, 2008); decimal price = AK47价格.标准价格 ##### (4) 误把内部状态当成了属性 如果某个性质是对象的非公开的内部状态,则应该从对象模型中删掉这个属性。 ##### (5) 过于细化 在分析阶段应该忽略那些对大多数操作都没有影响的属性。 ##### (6) 存在不一致的属性 往往是由于类的划分不准确造成,分解成两个不同的类。 经过筛选之后,得到ATM系统中各个类的属性,如图所示: ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20191030130247907.png) ### 10.3.5 识别继承关系 继承关系的建立实质上是知识抽取过程,它应该反映出一定深度的领域知识,因此必须有领域专家密切配合才能完成。 可使用两种方式建立继承(泛化)关系: #### 1. 自底向上 模拟人类归纳思维过程,从现有类抽象出共同性质泛化出父类。 例如,在ATM系统中,“远程事务”和“柜员事务”是类似的,可以泛化出父类“事务”; 可以从“ATM”和“柜员终端”泛化出父类“输入站”。 #### 2.自顶向下 模拟人类的演绎思维过程,从现有类细化成更具体的子类。 这个过程比较自然,但避免过细。 如图是增加了继承关系之后的ATM对象模型。 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20191030130232705.png) ### 10.3.6 反复修改 面向对象开发过程是反复修改、逐步完善的过程。   相对结构分析、设计技术而言,面向对象更容易实现反复修改、逐步完善的过程。   下面以ATM系统为例,讨论可能做的修改: #### 1) 分解“现金兑换卡”类 “现金兑换卡”它是ATM获得分行代码和卡号等数据的数据载体,又具有鉴别储户使用ATM的权限的卡的两个独立功能。 “现金兑换卡”类→“卡权限”和“现金兑换卡”两个类,将使每个类的功能更单一。 10.3.6 反复修改 #### 2) “事务”由“更新”组成 事务对账户的更新,指的是对账户所做的一个动作(取款、存款或查询)。 这个动作有自己的属性(类型、金额等),应该独立存在,应作为类。 3.把“分行”与“分行计算机”合并 “分行”与“分行计算机”对分析系统没有区别的意义→合并。类似合并“总行”和“中央计算机”。 如图是修改后的ATM对象模型,更简单、清晰。 ## 10.4 建立动态模型 动态模型,UML状态图、结构化分析状态转换图。 对于仅存储静态数据的系统(例如数据库)来说,动态模型并没有什么意义,例如:MIS。 在开发交互式系统时,动态模型起着很重要的作用,具体步骤: 第1步,编写交互行为的脚本; 第2步,提取事件,确定事件两侧的对象(触发事件者和事件目标者)——事件跟踪图; 第3步,排列事件次序,确定各对象可能状态及状态间的转换关系,用状态图描绘它们。 第4步,比较各个对象的状态图,检查它们之间的一致性,确保事件之间的匹配。 ### 10.4.1 编写脚本 脚本是系统在某一执行期间内出现的一系列事件,是对用户(或设备)与目标系统之间的交互过程的描述。 脚本的目的:对目标系统的行为进一步的认识。 脚本的内容:   ①编写正常情况的脚本;   ②考虑特殊情况,例如输入或输出的数据为最大值(或最小值) ;   ③考虑出错情况,例输入的值为非法值或响应失败。 ### 10.4.2 设想用户界面 用户界面不是分析阶段考虑的重点(信息流和控制流作为重点),但是也需要考虑用户界面,确保能够完成全部必要的信息交换,而不会丢失重要的信息。 图是初步设想出的ATM界面格式。 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20191030130208274.png) ### 10.4.3 画事件跟踪图 状态图的绘制步骤:   ①完整、正确的脚本;   ②事件跟踪图(简易的UML顺序图);   ③状态图。 因为,脚本用自然语言书写,阅读时会有二义性。   确定事件,分析各脚本(正常、异常),提取所有外部事件(包括系统与用户(或外部设备)交互的所有信号、输入、输出、中断、动作等等)。正常事件易找,异常事件和出错条件易遗漏。传递信息的对象的动作、大多数对象到对象的交互行为都对应着事件。   如图所示,事件跟踪图(简易顺序图)。 竖线——对象,横线——事件,箭头——作用方向。 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/2019103013014562.png) ### 10.4.4 画状态图 状态图描绘事件与对象状态的关系。   集中精力仅考虑具有重要交互行为的那些类。 事件序列引出状态序列。 具体方法:   选定事件跟踪图中的一条竖线(一个类对象),仅考虑指向该条竖线的那些箭头线(事件),将这些事件作为状态图中的有向边(即箭头线),边上标以事件名。   两个事件之间的间隔就是一个状态。   根据一张事件跟踪图画出状态图之后,再把其他脚本的事件跟踪图合并到已画出的状态图中。   例如ATM系统,在考虑正常情况后,需要考虑异常情况。   为此需在事件跟踪图中找出以前考虑过的脚本的分支点(例如“验证账户”就是一个分支点,因为验证的结果可能是“账户有效”,也可能是“无效账户”,然后把其他脚本中的事件序列并入已有的状态图中,作为一条可选的路径。 在ATM系统中:   “ATM”、“柜员终端”、“总行”和“分行” 都是主动对象,它们相互发送事件;   “现金兑换卡”、“事务”和“账户”是被动对象,并不发送事件。   “储户”和“柜员”虽然也是动作对象,但是,它们都是系统外部的因素,无须在系统内实现它们。   因此,只需要考虑“ATM”、“总行”、“柜员终端”和“分行”的状态图,如下各图(由于“柜员终端”同“ATM”类似,略)。 ATM类的状态图  ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20191030130039739.png) ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20191030130059439.png) ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20191030130108242.png) ### 10.4.5 审查动态模型 在完成了每个具有重要交互行为的类的状态图之后,应该检查系统级的完整性和一致性。 - 一般说来,每个事件都应该既有发送对象又有接受对象(发送者、接受者可以相同)。 - 没有前驱或没有后继的状态应该是交互序列的起点或终点,否则出错误。 - 应该审查每个事件,跟踪它对系统中各个对象所产生的效果,以保证它们与每个脚本都匹配。 以ATM系统为例。在总行类的状态图中,事件 “分行代码错”是由总行发出的,但是在ATM类的状态图中并没有一个状态接受这个事件。因此,在ATM类的状态图中应该再补充一个状态“do/显示分行代码错信息”,它接受由前驱状态“do/验证账户”发出的事件“分行代码错”,它的后续状态是“退卡”。 ## 10.5 建立功能模型 通常在建立了对象模型和动态模型之后再建立功能模型。 功能模型由一组数据流图组成。 其中的处理功能可以用IPO图(或表)、伪码等多种方式进一步描述。 ### 10.5.1 画出基本系统模型图图 ### 10.5.2 画出功能级流程图 10.12是ATM系统的基本系统模型。 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20191030125955128.png) ### 10.5.3 描述处理框功能 描述处理框的功能——要着重描述每个处理框所代表的功能,而不是实现功能的具体算法。 可利用IPO图、伪代码等。 ## 10.6 定义服务 - “对象”是由属性及对这些数据施加的操作(即服务)封装在一起构成的独立单元。 - 服务需要在建立了动态模型和功能模型基础之上确定服务。 在确定类中服务时应考虑如下: ### 10.6.1 常规行为 类中定义的每个属性都是可访问的,即都定义了读、写该类每个属性的操作(服务)。 但这些常规操作一般不在类图中显示表示(设计时考虑即可)。 ### 10.6.2 从事件导出的操作 在状态图中,事件是某对象接收到的消息,根据消息选择符指定的操作修改对象状态(即属性值),并启动相应的服务。   例如ATM系统:    发往ATM对象的事件“中止”,启动该对象的服务“打印账单”;    发往分行的事件“请分行验卡”启动该对象的服务“验证卡号”;    事件“处理分行事务”启动分行对象的服务    “更新账户”。 ### 10.6.3 与数据流图中处理框对应的操作数据流图中处理框都与对象上的操作相对应。 综合考虑状态图和数据流图,确定对象应提供服务。   例如,在ATM系统中,从状态图上看出分行对象应该提供“验证卡号”服务,而在数据流图上与之对应的处理框是“验卡”,根据实际应该完成的功能看,该对象提供的这个服务应该是“验卡”。 (四)利用继承减少冗余操作利用继承机制继承服务,减少定义的服务数目。 只要不违背领域知识和常识,就尽量抽取出相似类的公共属性和操作,以建立这些类的新父类,并在类等级的不同层次中正确地定义各个服务。 # 11. 面向对象设计 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20200810162346164.png) 面向对象设计 设计就是将分析阶段得到的需求转变成符合成本和质量要求的、抽象的系统实现方案的过程。 从面向对象分析到面向对象设计(OOD),是用面向对象观点建立求解域模型、逐渐扩充模型的过程。 面向对象分析和设计在软件开发过程中的界限是模糊的,分析和设计活动是一个多次反复迭代的过程。 结构化方法的设计分为总体设计和详细设计,面向对象设计可细分为系统设计和对象设计(本书不区分两者)。 面向对象开发过程的应用生存期模型 ## 11.1 面向对象设计的准则 结构化软件设计基本原理仍然成立: 对于面向对象方法的新特点,增加了下列的面向对象设计准则。 ### 11.1.1 模块化 因为对象就是模块,所以面向对象软件开发模式完全符合系统的模块化设计原理。 对象模块将数据结构和操作紧密地结合在一起。 ### 11.1.2 抽象 类是一种抽象数据类型: 通过类提供的公共接口及合法操作符,对类实例中包含的数据进行操作。使用者无须知道这些操作符的实现算法和类中数据元素的具体表示方法,可以使用类中定义的数据。 ### 11.1.3 信息隐藏 在面向对象方法中,通过对象的封装性实现了信息隐藏: 类结构分离了接口与实现,用户来说,类属性的表示方法和类操作的实现算法都应设计成是隐藏的,从而支持信息隐藏。 ### 11.1.4 弱耦合 在面向对象方法中,对象是最基本的模块,耦合是指各对象之间相互关联的紧密程度。 对象不可能是完全孤立的,当两个对象必须相互联系相互依赖时,应该通过类的协议(即公共接 口)实现耦合,而不应该依赖于类的具体实现细节。 对象之间的耦合可分为两大类: #### 1) 交互耦合 如果对象之间的耦合通过消息连接来实现,则这种耦合就是交互耦合。 为使交互耦合尽可能松散,应该遵守下述准则: ①尽量降低消息连接的复杂程度。应该尽量减少消息中包含的参数个数,降低参数的复杂程度。 ②减少对象发送(或接收)的消息数。 #### 2) 继承耦合 继承是一般化类与特殊类之间耦合的一种形式。 与交互耦合相反,应该提高继承耦合程度。 从本质上看,通过继承关系结合起来的基类和派生类,构成了系统中粒度更大的模块。因此,它们彼此之间应该结合得越紧密越好。 为获得紧密的继承耦合,在设计时应该使特殊类尽量多继承并使用其一般化类的属性和服务。 ### 11.1.5 强内聚 内聚衡量一个模块内各个元素彼此结合的紧密程度。 在设计时应该力求做到高内聚。 在面向对象设计中存在下述3种内聚: (1) 服务内聚 一个服务应该完成一个且仅完成一个功能。 (2) 类内聚 设计类的原则:一个类应只有一个用途,它的属性和服务应该是高内聚的。 (3)一般—特殊内聚 类对象的关系中,一般与特殊的关系称为泛化 (Generalization)与特化(Specialization)联系。 设计出的一般-特殊结构,应该符合多数人的概念,应该是对相应的领域知识的正确抽取。 紧密的继承耦合与高度的一般-特殊内聚是一致的。 ### 11.1.6 可重用 软件重用基本上从设计阶段开始。 重用有两方面的含义: 一是尽量使用已有的类(包括开发环境提供的类库,及以往开发类似系统时创建的类); 二是如果确实需要创建新类,则在设计这些新类的协议时,应该考虑将来的可重复使用性。 ## 11.2 启发规则 启发规则能提高面向对象设计的质量。 ### 11.2.1 设计结果应该清晰易懂 使设计结果清晰、易读、易懂,是提高软件可维护性和可重用性的重要措施。 保证设计结果清晰易懂的主要因素如下: #### 1) 用词一致 应该使名字与它所代表的事物一致,而且应该尽量使用人们习惯的名字。不同类中相似服务的名字应该相同。 #### 2) 使用已有的协议 如果开发同一软件的其他设计人员已经建立了类的协议,或者在所使用的类库中已有相应的协议,则应该使用这些已有的协议。 #### 3) 减少消息模式的数目有标准的消息协议,应该遵守这些协议。 自己建立消息协议,则应尽量减少消息模式的数目,尽可能使消息具有一致的模式,利于理解。 #### 4) 避免模糊的定义 一个类的用途应该是有限的,而且应该从类名可以较容易地推想出它的用途。 ### 11.2.2 一般-特殊结构的深度应适当应该使类等级中包含的层次数适当。 在一个中等规模(大约包含100个类)的系统中,类等级层次数应保持为7±2。 不应该仅仅从方便编码的角度出发随意创建派生类,应该使一般-特殊结构与领域知识或常识保持一致。 ### 11.2.3 设计简单的类 应尽量设计小而简单的类,以便于开发和管理。当类很大的时候,要记住它的所有服务是非常困难的。 经验表明,如果一个类的定义不超过一页纸(或两屏),则使用这个类是比较容易的。为使类保持简单,应该注意以下几点。 #### 1) 避免包含过多的属性 属性过多表明这个类过分复杂了,它所完成的功能可能太多了。 #### 2) 有明确的定义 为了使类的定义明确,分配给每个类的任务应该简单,最好能用一两个简单语句描述它的任务。 #### 3) 尽量简化对象之间的合作关系 多个对象合做一件事,类的简明性和清晰性受到影响。 #### 4) 不要提供太多服务一个类提供的公共服务不超过7个。 当遵循上述原则设计出大量较小的类时,通过划分“主题”减小复杂性。 ## 11.3 软件重用 ### 11.3.1 概述 #### 1) 重用 重用也叫复用,软件重用可分为以下3个层次: (1) 知识重用(例如,软件工程知识的重用)。 (2) 方法和标准的重用(例如,面向对象方法或国家制定的软件开发规范的重用)。 (3) 软件成分的重用。 前两个重用层次属于知识工程研究的范畴,本节仅讨论软件成分重用问题。 #### 2) 3个级别: 软件成分的重用级别软件成分的重用可以进一步划分成以下3个级别: ##### (1) 代码重用 代码重用可采用下列几种形式中的任何一种:源代码剪贴 缺点——复制或修改原有代码时可能出错,存在严重的配置管理问题,人们几乎无法跟踪原始代码块多次修改重用的过程。利用继承机制重用类库中的类时,无须修改已有的代码,就可以扩充或具体化在库中找出的类,因此,基本上不存在配置管理问题。 ##### (2) 设计结果重用 设计结果重用——重用某个软件系统的设计模型(即求解域模型)。 这个级别的重用有助于把一个应用系统移植到完全不同的软硬件平台上。 ##### (3) 分析结果重用 重用某个系统的分析模型——特别适用于用户需求未改变,但系统体系结构发生了根本变化的场合。 #### 3) 典型的可重用软件成分 可能被重用的软件成分主要有以下10种: (1)项目计划 (2)成本估计 (3)体系结构 (4)需求模型和规格说明 (5)设计 (6)源代码 (7)用户文档和技术文档 (8)用户界面 (9)数据 (10)测试用例 面向对象中的“类”是比较理想的可重用软构件(类构件)。 ### 11.3.2 类构件 类构件有3种重用方式: 实例重用 继承重用 多态重用 #### 1) 可重用软构件应具备的特点 (1) 模块独立性强 (2) 具有高度可塑性 提供为适应特定需求而扩充或修改已有构件的机制,且这种机制用起来简单方便。 提供为适应特定需求而扩充或修改已有构件的机制,且这种机制用起来简单方便。0 (3)接口清晰、简明、可靠 软构件应提供清晰、简明、可靠的对外接口,而且还应有详尽的文档说明,以方便用户使用。 #### 2) 类构件的重用方式 (1) 实例重用 形式1:使用适当的构造函数,按需创建类的实例。然后发送适当的消息,启动相应的服务,完成需要完成的工作。 形式2:由几个简单的对象作为类的成员,创建出一个较复杂的类。 (2) 继承重用 当已有类构件不能通过实例重用满足系统需求时,继承重用提供了一种手段,安全地修改已有类构件达到重用的目的。 这样做有下述两个好处: A) 子类继承父类,降低了每个类构件的接口复杂度、提高每个子类的可理解性,而且为软件开发人员提供了更多可重用的类构件。 B) 为多态重用奠定了良好基础。 (3) 多态重用 由于基类与派生类的许多对外接口是相同的,多态性使对象的对外接口更加一般化——降低了消息连接的复杂程度,提供了一种简便可靠的软构件组合机制。 系统运行时,根据接收消息的对象类型,由多态性机制启动正确的方法,去响应一个一般化的消息,从而简化了消息界面和软构件连接过程。 ### 11.3.3 软件重用的效益 * 质量 随着每一次重用,构件的质量也会随之改善。 没有缺陷的软件才能重用。 * 生产率 重用使得创建计划、模型、文档、代码和数据所需花费的时间将减少,生产率得到了提高. 30%~50%的重用大约可以导致生产率提高25% ~40%。 * 成本 软件重用净成本节省估算:C=Cs-Cr-Cd Cs~没有重用时所需要的成本; Cr~与重用相关联的成本; Cd~交付给客户的软件的实际成本。 ## 11.4 系统分解 系统分解成若干个小的部分,然后再分别设计每个部分——降低设计的难度,有利于分工协作,也有利于维护人员对系统理解和维护。 子系统的数目应该与系统规模匹配,应设计简单、明确的接口。 在划分和设计子系统时,应该尽量减少子系统彼此间的依赖性。 面向对象设计模型同分析模型相同,也由主题层、类与对象层、结构层、属性层、服务层5个层次组成。 ### 11.4.1 子系统之间的两种交互方式 在软件系统中,子系统之间的交互有两种可能的方式,分别是客户-供应商(Client-supplier)关系和平等伙伴(peer-to-peer)关系。 1. 客户-供应商关系 作为“客户”的子系统调用作为“供应商”的子系统,后者完成某些服务工作并返回结果。 使用这种交互方案,作为客户的子系统必须了解作为供应商的子系统的接口,然而后者却无须了解前者的接口,因为任何交互行为都是由前者驱动的。 2. 平等伙伴关系 每个子系统都可能调用其他子系统,因此,每个子系统都必须了解其他子系统的接口。 同客户-供应商方案比较,各子系统之间的交互更复杂,而且还可能存在通信环路,从而使系统难于理解,容易发生不易察觉的设计错误。 总的说来,单向交互比双向交互更容易理解,也更容易设计和修改——应该尽量使用客户-供应商关系。 ### 11.4.2 组织系统的两种方案 把子系统组织成完整的系统时,有水平的层次组织和垂直的块组织两种方案可供选择。 #### 1) 层次组织 把软件系统组织成一个层次系统,每层是一个子系统。上层在下层的基础上建立,下层为实现上层功能而提供必要的服务。 同一层内所包含的对象,彼此间相互独立; 不同层次上的对象,彼此间往往有关联。 上、下层之间明显存在客户-供应商关系。低层子系统提供服务,相当于供应商,上层子系统使用下层提供的服务,相当于客户。 #### 2) 块状组织 软件系统垂直地分解成若干个相对独立的、弱耦合的子系统,一个子系统相当于一块,每块提供一种类型的服务。 利用层次和块的各种可能的组合,可以成功地由多个子系统组成一个完整的软件系统。 当混合使用层次结构和块状结构时,同一层次可以由若干块组成,而同一块也可以分为若干层。 例如,图表示一个应用系统的组织结构,这个应用系统采用了层次与块状的混合结构。 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20191030142925529.png) #### 3) 设计系统的拓扑结构 用子系统组成完整的系统时,设计者应该采用与问题结构相适应的、尽可能简单的拓扑结构,以减少子系统之间的交互数量。 典型的拓扑结构有管道形、树形、星形等。 ## 11.5 设计问题域子系统 问题域:直接负责实现客户需求的子系统。 面向对象方法在分析与设计之间并没有明确的分界线,对于问题域子系统来说,情况更是如此。 但是分析工作可以而且应该与具体实现无关,设计工作则在很大程度上受具体实现环境的约束。 (使用的编程语言、可用的软构件库(主要是类 库)、编程经验等)。  将结构化分析模型转换为软件设计 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/2019103014321775.png)  面向对象的分析与设计模型转换关系 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20191030143313763.png) 面向对象分析——问题域精确模型,为设计问题域子系统奠定了良好的基础,建立了完整的框架。 尽量保持面向对象分析所建立的问题域结构。 通常,面向对象设计仅需从实现角度对问题域模型做一些补充或修改: 增添、合并或分解类与对象、属性及服务,调整继承关系等等。 当问题域子系统过分复杂庞大时,应该把它进一步分解成若干个更小的子系统。 下面介绍,在面向对象设计过程中,可能对面 向对象分析所得出的问题域模型做的补充或修改。 ### 11.5.1 调整需求两种情况修改 一是用户需求或外部环境发生了变化; 二是分析员对问题域理解不透彻或缺乏领域专家帮助,以致面向对象分析模型不能完整、准确地反映用户的真实需求。 ### 11.5.2 重用已有的类 重用已有类的典型过程如下:   (1) 选择已有类作为候选类,标出候选类中对本问题无用的属性和服务,尽量重用那些能使无用的属性和服务降到最低程度的类。   (2) 通过被重用的已有类→派生出问题域类。   (3) 问题域类中从已有类继承来的属性和服务,不用定义它们(在问题域类内)。   (4) 修改与问题域类相关的关联,必要时改为与被重用的已有类相关的关联。 ### 11.5.3 把问题域类组合在一起设计中,通常引入根类而把问题域类组合起来。 在没有更先进的组合机制可用时,才采用引入根类的组合方法。 ### 11.5.4 增添一般化类以建立协议 在设计中,一些具体类需要有一个公共的协议,即都需要定义一组类似的服务。 在这种情况下可以引入一个附加类(例如,根类),以便建立这个协议(即命名公共服务集合,这些服务在具体类中仔细定义)。 ### 11.5.5 调整继承层次 使用多重继承机制时,应该避免出现属性及服务的命名冲突。   下面通过例子说明避免命名冲突的方法。   图11.4是一种多重继承模式的例子,这种模式可以称为窄菱形模式。使用这种模式时出现属性及服务命名冲突的可能性比较大。 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20191030145737622.png) ### 11.5.6 ATM系统实例 图描绘了上章给出的ATM系统的问题域子系统的结构。   由于在面向对象分析过程中已经对ATM系统做了相当仔细的分析,而且假设所使用的实现环境能完全支持面向对象分析模型的实现,因此,在面向对象设计阶段无须对已有的问题域模型作实质性的修改或扩充。 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/2019103014590037.png)           图11.7 ATM系统问题域子系统的结构 ## 11.6 设计人机交互子系统 在面向对象分析中,对用户界面需求做了初步分析,在设计中,则应该对系统的人机交互子系统进行详细设计,以确定人机交互的细节:指定窗口和报表的形式、设计命令层次等项内容。 设计人机交互子系统的策略简介如下: ### 11.6.1 分类用户 通常从下列几个不同角度进行分类:按技能水平分类(新手、初级、中级、高级)。   按职务分类(总经理、经理、职员)。   按所属集团分类(职员、顾客)。 ### 11.6.2 描述用户仔细了解将使用系统的每类用户的情况 用户类型;使用系统欲达到的目的;特征(年龄、性别、受教育程度、限制因素等);关键的成功因素(需求、爱好、习惯等);技能水平;完成本职工作的脚本。 ### 11.6.3 设计命令层次 设计命令层次的工作通常包含以下几项内容。 1.研究现有的人机交互含义和准则 设计图形用户界面时,应该保持与普通Windo ws应用程序界面相一致,并遵守广大用户习惯的约定,这样才会被用户接受和喜爱。 2.确定初始的命令层次 所谓命令层次,实质上是用过程抽象机制组织起来的、可供选用的服务的表示形式。设计命令层次时,通常先从对服务的过程抽象着手,然后再进一步修改它们,以适合具体应用环境的需要。 3. 精化命令层次 为进一步修改完善初始的命令层次,应该考虑下列一些因素: 次序:仔细选择每个服务的名字,并在命令层的每一部分内把服务排好次序。 整体-部分关系:寻找在这些服务中存在的整体部分模式,这样做有助于在命令层中分组组织服务。 宽度和深度:由于人的短期记忆能力有限,命令层次的宽度和深度都不应该过大。 操作步骤:用尽量少的单击、拖动和击键组合来表达命令,且应为高级用户提供简捷的操作。 4. 设计人机交互类 人机交互类与所使用的操作系统及编程语言密切相关。例如,在Windows环境下运行的Visual C++语言提供了MFC类库,设计人机交互类时,往往仅需从MFC类库中选出一些适用的类,然后从这些类派生出符合自己需要的类就可以了。   任务:通常指软件功能需求所载明的软件应完成的功能。   任务管理器:用于协调各个功能的调度和执行。 分析阶段(OOA)未涉及系统的具体实现细节,未引入任务管理部件。 设计阶段(OOD )引入任务管理部件原因:   ①多用户、多任务系统上开发应用程序的需要;   ②通过任务管理部件协调各个子系统之间的通信和协同;    设计工作的一项重要内容就是,确定哪些是必须同时动作的对象(并发),哪些是相互排斥的对象 (非并发),然后进一步设计任务管理子系统。 ## 11.7 设计任务管理子系统 任务:通常指软件功能需求所载明的软件应完成的功能。   任务管理器:用于协调各个功能的调度和执行。 分析阶段(OOA)未涉及系统的具体实现细节,未引入任务管理部件。 设计阶段(OOD )引入任务管理部件原因: ①多用户、多任务系统上开发应用程序的需要; ②通过任务管理部件协调各个子系统之间的通信和协同; 设计工作的一项重要内容就是,确定哪些是必须同时动作的对象(并发),哪些是相互排斥的对象(非并发),然后进一步设计任务管理子系统。 与任务管理(控制驱动)部分设计有关的技术 : * 分析并发性 分析中的动态模型(状态图)是分析并发性的主要依据。   并发:两个对象同时接受事件、彼此间不存在交互。   可将若干个非并发的对象归并到一条控制线(是一条遍及状态图集合的路径)上,在这条控制线上每次只有一个对象是活动的。   在计算机系统中用任务(task)实现控制线,任务是进程(process)的别名。通常把多个任务的并发执行称为多任务。   对于某些应用系统来说,通过划分任务,可以简化系统的设计及编码工作。 * 设计任务管理子系统 常见的任务有: 事件驱动型任务 时钟驱动型任务 优先任务 关键任务 协调任务等。 设计任务管理子系统,包括确定各类任务并把任务分配给适当的硬件或软件去执行。 1.确定事件驱动型任务 某些任务是由事件驱动的,这类任务可能主要完成通信工作。 事件通常是表明某些数据到达的信号。 2.确定时钟驱动型任务 某些任务每隔一定时间间隔就被触发以执行某些处理,例如,某些设备需要周期性地获得数据;某些人机接口、子系统、任务、处理器或其他系统也可能需要周期性地通信。在这些场合往往需要使用时钟驱动型任务。 3.确定优先任务 优先任务可以满足高优先级或低优先级的处理需求:   高优先级:某些服务具有很高的优先级,为了在严格限定的时间内完成这种服务,可能需要把这类服务分离成独立的任务。   低优先级:与高优先级相反,有些服务是低优先级的,属于低优先级处理。设计时可能用额外的任务把这样的处理分离出来。 4.确定关键任务 关键任务是有关系统成功或失败的关键处理,这类处理通常都有严格的可靠性要求。在设计过程中可能用额外的任务把这样的关键处理分离出来,以满足高可靠性处理的要求。 5.确定协调任务 当系统中存在3个以上任务时,就应该增加一个任务,用它作为协调任务。引入协调任务会增加系统的总开销(增加从一个任务到另一个任务的转换时间),但是引入协调任务有助于把不同任务之间的协调控制封装起来。 6.尽量减少任务数 必须仔细分析和选择每个确实需要的任务。应该使系统中包含的任务数尽量少。 过多的任务加大了设计工作的技术复杂度,并使系统变得不易理解,从而也加大了系统维护的难度。 7.确定资源需求 使用多处理器,主要是为了满足高性能的需求。 设计者必须通过计算系统载荷(即每秒处理的业务数及处理一个业务所花费的时间),来估算所需要的CPU(或其他固件)的处理能力。 ## 11.8 设计数据管理子系统 数据管理子系统是系统存储或检索对象的基本设施或组织形式,它建立在某种数据存储管理系统之上,并且隔离了数据存储管理模式(文件、关系数据库或面向对象数据库)的影响。 ### 11.8.1 选择数据存储管理模式 不同的数据存储管理模式有不同的特点,适用范围也不相同,应根据应用系统的特点选择适用的数据存储管理模式。 * 文件管理系统 文件管理系统是操作系统的一个组成部分,使用它长期保存数据具有成本低和简单等特点。   但是,文件操作的级别低,为提供适当的抽象级别还必须编写额外的代码(例如:考试系统的答案,使用文本文件类型)。   此外,不同操作系统的文件管理系统往往有明显差异。 * 关系数据库管理系统 关系数据库管理系统的理论基础是关系代数,它不仅理论基础坚实而且有下列一些主要优点:   (1) 提供了各种最基本的数据管理功能(例如,中断恢复,多用户共享,多应用共享,完整性,事务支持等)。   (2) 为多种应用提供了一致的接口。   (3) 标准化的语言(大多数商品化关系数据库管理系统都使用SQL语言)。 关系数据库管理系统通常都相当复杂,缺点: * (1) 运行开销大:即使只完成简单的事务(例如,只修改表中的一行),也需要较长的时间。 * (2) 不能满足高级应用的需求:关系数据库管理系统是为商务应用服务的,商务应用中数据量虽大但数据结构却比较简单。关系数据库管理系统很难用在数据类型丰富或操作不标准的应用中。 * (3) 与程序设计语言的连接不自然:SQL语言支持面向集合的操作,是一种非过程性语言;然而大多数程序设计语言本质上却是过程性的,每次只能处理一个记录。 * 面向对象数据库管理系统 面向对象数据库管理系统是一种新技术,主要有两种设计途径:   ➢扩展的关系数据库管理系统   ➢扩展的面向对象程序设计语言 + (1) 扩展的关系数据库管理系统是在关系数据库的基础上,增加了抽象数据类型和继承机制,增加了创建及管理类和对象的通用服务。 + (2) 扩展的面向对象程序设计语言扩充了面向对象程序设计语言的语法和功能,增加了在数据库中存储和管理对象的机制。 ### 11.8.2 设计数据管理子系统 设计数据管理子系统,既需要设计数据格式又需要设计相应的服务。 #### 1) 设计数据格式 设计数据格式的方法与所使用的数据存储管理模式密切相关,分别介绍如下: (1) 文件系统 定义第一范式表:列出每个类的属性表;把属性表规范成第一范式,从而得到第一范式表的定义。 为每个第一范式表定义一个文件;测量性能和需要的存储容量;修改原设计的第一范式以满足性能和存储需求;必要时把泛化结构的属性压缩在单个文件中,以减少文件数量。 必要时把某些属性组合在一起,并用某种编码值表示这些属性,而不再分别使用独立的域表示每个属性。这样做可以减少所需要的存储空间,但是增加了处理时间。 (2) 关系数据库管理系统 定义第三范式表:列出每个类的属性表;把属性表规范成第三范式,从而得出第三范式表的定义。 为每个第三范式表定义一个数据库表。 测量性能和需要的存储容量。 修改先前设计的第三范式,以满足性能和存储需求。 (3) 面向对象数据库管理系统 扩展的关系数据库途径:使用与关系数据库管理系统相同的方法。 扩展的面向对象程序设计语言途径:不需要规范化属性的步骤,因为数据库管理系统本身具有把对象值映射成存储值的功能。 #### 2) 设计相应的服务 某个类的对象需要存储,则在这个类中增加一个属性和服务→用于完成存储对象自身的工作。   此时增加的属性和服务作为“隐含”的属性和服务,设计模型中的属性和服务层中不必显式地表示它们,仅需在关于类与对象的文档中描述它们。 ## 11.9 设计类中的服务 ### 11.9.1 设计类中应有的服务 综合考虑对象模型、动态模型和功能模型是正确确定类中应有的服务的前提。   对象模型通常在每个类中只列出很少几个最核心的服务。设计者必须把动态模型中对象的行为以及功能模型中的数据处理,转换成由适当的类所提供的服务。   状态图描绘了一类对象的生命周期,状态转换是执行对象服务的结果。   功能模型指明了系统必须提供的服务。状态图中状态转换所触发的动作,在功能模型中有时可能扩展成一张数据流图。   数据流图中的某些处理可能与对象提供的服务相对应,下列规则有助于确定操作的目标对象:   (1) 如果某个处理的功能是从输入流中抽取一个值,则该输入流就是目标对象(处理是针对输入流的,例如:学生成绩-计算总成绩)。   (2) 如果一个处理有相同类型的输入流和输出流,而且大部分输出值是输入流的更新版本,那么输入/输出是目标(原工资清单-调工资-现工资清单)。   (3) 如果一个处理由几个输入流得出输出值,那么该操作是一个输出类上的类操作(构造函数)。   (4) 如果一个处理的输入来自或输出到达一个数据存储或施动者,那么数据存储或施动者是处理的一个目标。 ### 11.9.2 设计实现服务的方法 设计实现服务的方法,还应需完成如下几项工作。  1. 设计实现服务的算法设计实现服务的算法时,应考虑下列几个因素:    (1) 算法复杂度         通常选用复杂度较低(即效率较高)的算法,但也         不要过分追求高效率,应以能满足用户需求为准。       (2) 容易理解与容易实现       (3) 易修改  2. 选择数据结构    设计过程中需要选择能够方便、有效地实现算法的数据结构。  3. 定义内部类和内部操作    在设计过程中,可能需要增添在需求陈述中没有提到的类,主要用来存放在执行算法过程中所得出的某些中间结果-内部类。       在高层操作分解中可能会发现新的低层操作,这些低层操作必须在对象设计阶段定义,因为它们大多数都外部不可见-内部操作的。 ## 11.10 设计关联 对象模型中的关联指定了对象相互间的访问路径。 在面向对象设计过程中,必须确定实现关联的具体策略。 为了更好地设计实现关联的途径,首先应该分析使用关联的方式。    1. 关联的遍历在应用系统中,使用关联有两种可能的方式:单向遍历和双向遍历。   在应用系统中,某些关联只需要单向遍历,这种单向关联实现起来比较简单,另外一些关联可能需要双向遍历,双向关联实现起来稍微麻烦一些。    2. 实现单向关联   用指针可以方便地实现单向关联。   如果关联的重数是一元的(如图11.8所示),则实现关联的指针是一个简单指针。 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/2019103015491783.png)                   用指针实现单向关联 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20191030154950426.png)               图11.9 用指针实现双向关联 对象模型中的关联指定了对象相互间的访问路径。   在面向对象设计过程中,必须确定实现关联的具体策略。   为了更好地设计实现关联的途径,首先应该分析使用关联的方式。 ### 11.10.1 关联的遍历 在应用系统中,使用关联有两种可能的方式:      单向遍历和双向遍历。   在应用系统中,某些关联只需要单向遍历,这种单向关联实现起来比较简单,另外一些关联可能需要双向遍历,双向关联实现起来稍微麻烦一些。 ### 11.10.2 实现单向关联 用指针可以方便地实现单向关联。 如果关联的重数是一元的(如图11.8所示),则实现关联的指针是一个简单指针。 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20191030151338507.png) #### 11.10.3 实现双向关联 许多关联都需要双向遍历,当然,两个方向遍历的频度往往并不相同。实现双向关联有下列3种方法:    (1) 只用属性实现一个方向的关联,当需要反向遍历时就执行一次正向查找。如果两个方向遍历的频度相差很大,而且需要尽量减少存储开销和修改时的开销,则这是一种很有效的实现双向关联的方法。    (2) 两个方向的关联都用属性实现。具体实现方法已在前面讲过,如图11.9所示。这种方法能实现快速访问,但是,如果修改了一个属性,则相关的属性也必须随之修改,才能保持该关联链的一致性。当访问次数远远多于修改次数时,这种实现方法很有效。    (3) 用独立的关联对象实现双向关联。关联对象不属于相互关联的任何一个类,它是独立的关联类的实例,如图所示。 ![在这里插入图片描述](https://gitee.com/fakerlove/picture_1/raw/master/20191030151436501.png) #### 11.10.4 关联对象的实现 可以引入一个关联类来保存描述关联性质的信息,关联中的每个连接对应着关联类的一个对象。   实现关联对象的方法取决于关联的重数。对于一对一关联来说,关联对象可以与参与关联的任一个对象合并。对于一对多关联来说,关联对象可以与“多”端对象合并。如果是多对多关联,则关联链的性质不可能只与一个参与关联的对象有关,通常用一个独立的关联类来保存描述关联性质的信息,这个类的每个实例表示一条具体的关联链及该链的属性。 # 12. 面向对象实现 # 13. 软件项目管理