软件设计在软件工程中处于技术核心,其目的是把需求分析模型转变为设计模型,以知道软件的实现,本章讲解软件设计的基本原则和基本实践
本文参考教材:沈备军老师的《软件工程原理》
软件设计概述
软件设计分为两个阶段,分别为架构设计和详细设计
架构设计又称概要设计,主要包括选择质量属性的设计策略、确定合适的架构风格和设计模式、定义软件的主要结构元素–模块、接口设计
选择质量属性的设计策略:
一个良好的软件需要支持多种质量属性,其中有些属性在软件运行时能展现出来,例如性能、安全保密性、可用性、功能和易用性,而有些则不能,比如可维护性、可移植性和可测试性等
确定合适的架构风格:
架构风格是一种通用的架构模式,它定义了系统中组件的组织方式和它们之间的相互作用。常见的架构风格包括层次结构、客户端-服务器、发布-订阅等
定义模块:
在架构设计中,需要将系统分解为各个模块,每个模块负责特定的功能。这些模块应该具有高内聚(模块内部功能相关联)和低耦合(模块间关系松散)的特性
接口设计:
描述了软件和协作系统之间、软件和使用人员之间是如何通信的,包含软件模块间的内部接口、模块和协作系统之间的外部接口、使用人员和软件的接口
详细设计又称构件级设计,它在软件架构的基础上定义各模块的内部细节,例如数据结构、算法、控制流,常常只影响单个模块的实现
如果说需求工程解决“做什么”的问题,则设计工程是解决“怎么做”的问题
软件设计的原则
1.抽象
软件开发过程实际上就是对软件抽象层次的一次次细化的过程,上层是下层的抽象,下层是上层的分解,最高层的抽象程度最高
主要的抽象手段:数据抽象、过程抽象、对象抽象
2.分解和模块化
分解是控制复杂性的另一种有效方法,软件设计使用分解实现模块化设计,将一个复杂的软件系统自顶向下分解成若干模块,分别完成不同功能,最后将所有模块组装,形成整体
如果模块间互相独立,模块越小,则每个模块工作量越低,总共的工作量减少,但当模块数增加,模块间联系也增加,使得总共的工作量增加
3.封装和信息隐藏
如何进行上述的分解?封装和信息隐藏原则指出:分解得到的每个模块的实现细节对其它模块来说是隐藏的,即模块包含的信息对于不需要这些信息的模块来说不能访问。这样可以提高模块独立性
4.高内聚和低耦合
模块独立性是模块化追求的目标,也是软件质量的关键。模块化程度高,则软件容易开发、同时软件也比较容易测试和维护
衡量模块独立性有两个指标:内聚和耦合
内聚,是一个模块内部各个元素彼此结合的紧密程度
耦合,是模块之间的相对独立性
模块独立性追求:高内聚和低耦合
软件质量属性设计
常见软件质量属性包括可用性、可修改性、性能、安全性、可测试性、易用性 ,下面简要介绍这些属性并给出设计策略
可用性(Usability):
介绍: 系统正常运行程度,与系统故障及其后果有关
设计策略: 使用直观的界面设计,减少复杂性,提供明确的反馈,确保任务流畅完成。进行用户测试,根据反馈调整设计
可修改性(Maintainability):
介绍: 系统变更难易程度,与系统变更成本相关
设计策略: 使用清晰的代码结构和命名规范,采用设计模式提高可扩展性。注重文档,包括代码注释和技术文档
性能(Performance):
介绍: 系统对各种事件的响应速度,与时间控制相关
设计策略: 优化算法和数据结构,采用缓存技术,进行性能测试和分析,确保系统在合理的时间内完成操作
安全性(Security):
介绍: 安全性涉及系统对数据的保护以及对潜在威胁的防范
设计策略: 实施身份验证和授权机制,加密敏感数据,定期进行安全审计,考虑潜在的攻击面,使用安全的开发实践
可测试性(Testability):
介绍:系统通过测试发现软件缺陷的难易程度
设计策略: 设计模块时注重模块化,使用依赖注入,编写可自动化的测试用例,采用测试驱动开发(TDD)等实践
易用性(Portability):
介绍:用户通过系统完成期望任务的难易程度和系统提供的用户支持类型
设计策略:在系统运行时向用户提供反馈支持等
架构风格
选择合适的架构风格是软件设计的另一项重要任务,所谓架构风格,是“有关架构的约束条件的集合,它定义了满足这些约束条件的架构集或架构族”,可以将其看做是一种可以提供软件高层组织结构的原模型。简而言之,架构风格就是一种指导原则,告诉我们在构建软件时如何组织和设计各个部分,以便整个系统能够协同工作并实现预期的功能
分层架构风格
分层(Layer)架构风格针对大型系统,将其抽象为不同的层次,从而提供了一种进行系统分解的模式
分层架构的特点如下:
1.每一层提供了特定的设施,向高层提高服务,对高层屏蔽低层
2.每一层提供了与其他层有明确区分的功能,高层依赖于低层,低层不依赖于高层,最底层一般是硬件系统
分层架构的优点:
1.良好的可修改性:可以在不修改其他层的前提下,修改某一层,而不会产生涟漪效应
2.良好的复用性:如果使某一层向上提供的接口标准化,则这一层就可以当成标准的服务层在其他系统中复用
分层架构的缺点:
1.有损于性能:层数越多,开销越大,对性能的影响就越大。同时,对层的维护也需要比较大的开销
2.用户代码的控制力被削弱:用户代码控制力受限于用户接口
3.分层质量受抽象的影响:正确的抽象是保证分层质量的前提
上面是4S系统的分层架构,系统软件层主要提供低层的基础设施(操作系统、硬件等),中间件层提供utility类和分布式对象计算时屏蔽平台异构性的服务,业务相关性实现可复用的子系统,应用子系统层实现本应用特定的子系统
管道与过滤器架构
管道与过滤器(Pipes and Filters)架构是将软件系统分解成若干个过滤器和管道,过滤器的作用是对其输入数据进行处理,并产生输出数据,而管道的作用是将过滤器连接在一起
管道与过滤器架构的特点如下:
1.过滤器对输入数据进行处理时,不会保留任何历史信息
2.管道本身不对数据进行任何转换处理
管道与过滤器架构的优点如下:
1.便于进行过程式的系统功能分解
2.系统易于扩展和复用:增加新的过滤器或移除现有的过滤器比较容易
3.如果有多个过滤器可以并发执行,那么就有助于提高系统性能
管道与过滤器架构的缺点如下:
1.交互式程序不适合使用
2.数据流格式可能会降低系统性能
3.可能产生死锁和队列溢出等不利情况
比如运行下面的脚本:
$cat TestResults|sort|grep Good
形成的管道-过滤器架构如下:
黑板架构
黑板(Blackboard)架构专门针对没有确定的解决方法的问题,包含如下部分:
知识源:彼此独立,不直接交互,交互通过黑板完成
黑板数据结构:知识源会更新黑板数据,从而增量式地解决问题
控制流:由黑板状态驱动,知识源会在黑板数据被更新时根据其状态做出相应的响应
假设一群专家在一起设计一个应对小行星撞击地球的方案,这个问题以前没有被解决过,所以可以使用黑板系统,专家们只和黑板交互,他们只要认为黑板上的数据可以使用并产生新的数据,就获取这些数据,并将新数据更新到黑板中,最终设计出应对方案
比如,Expert1根据自己知识计算出小行星质量、速度和运行轨迹等,Expert2看到这些数据后,根据自己的知识算出小行星撞击地球后产生的爆炸当量,Expert3看到这些数据后,根据自己的知识计算出爆炸造成的破坏程度的数据,以此类推,在这个过程中,控制流完全由黑板中的数据驱动,这就是黑板架构的特点
客户端-服务器架构风格
客户端/服务器(Client/Server)架构风格将软件系统分成了两层,客户端和服务器端分别运行于独立的进程中,通过远程方法调用来沟通
客户端/服务器架构的特点如下:
1.客户端通常运行应用程序,通过多种网络通信协议以远程方法调用的方式与服务器通信
2.服务器支持客户端程序的并发访问,并提供安全、事务等控制机制
客户端/服务器架构的优点如下:
1.客户端可以充分利用客户端机器的计算资源,有效降低服务器端的负荷
2.能够提供复杂的用户界面
3.能够传输和缓存大量数据
客户端/服务器架构的缺点如下:
1.软件升级代价大
2.服务器端控制力减弱
在很多制造业企业中,生产制造管理系统大多就采用这种架构,因为在生产制造中会产生大量的数据,需要在用户机器和服务器之间传递大量数据,而且这种系统的用户控制界面通常非常复杂
三层架构
三层(Three-tiers)架构风格是在客户端和服务器端的数据库之间加入了一个中间层,有关业务逻辑的实现都放入这一层中,三层架构中包括如下三层:
1.表示层:向用户呈现界面,并接受用户请求发送给业务逻辑层
2.业务逻辑层:负责执行业务逻辑以处理用户请求,并调用数据访问层提供的持久性操作
3.数据访问层:负责执行数据库持久性操作
三层架构的优点:
1.各层逻辑分工明确,有利于开发人员分工
2.各层逻辑严格独立分离,有利于系统维护和复用
三层架构的缺点:
1.有些修改会贯穿所有层,带来高昂的代价
2.数据访问层的潜在问题,数据与数据库中的数据存在同步问题需要解决,对象与关系之间有可能存在不匹配的问题等
值得注意的是,三层或多层风格的层(Tiers)和上面分层风格的层(Layer)是两个不同的概念,前者是物理意义上的层,注重概念分布,后者是逻辑意义上的层,注重抽象和复用
MVC风格
MVC风格与三层架构风格类似,将软件系统分成3个互相关联的部分,包含以下三个部分:
1.视图:根据模型生成提供给用户的交互界面,不同的视图可以对相同的数据产生不同的界面,视图还负责将用户界面上的输入数据发送给控制器
2.模型:管理系统中存储的数据和业务规则,执行相应的计算功能
3.控制器:接受用户输入,通过调用模型获得响应,通知视图进行用户界面的更新
可以看出,MVC架构和三层架构存在下列的区别:
1.MVC架构的目标是将系统的模型、视图和控制器强制性地完全分离,从而使同一个模型可以使用不同的视图来表现,而计算模型可以使用不同的视图来表现。而三层架构的目标是将系统按照任务类型划分成不同的层次,从而可以将计算任务分布到不同的进程中执行,以提高系统的处理能力
2.模型包含了业务逻辑和数据访问逻辑,而在三层架构中这属于两个层的任务
微内核架构
微内核(Micro-kernel)概念来源于操作系统领域,微内核是提供了操作系统核心功能的内核,需要很小的空间便可以启动,向用户提供了标准接口,使得用户能够按照模块化的方式去扩展功能。
在软件架构领域,这种思想也是适用的,我们设计一个包含核心概念的内核,提供一些标准接口,就可以通过这些接口安装其它的扩展功能,比如下面的JBoss的微内核架构
从JBoss的例子中可以看出,在微内核架构中,微内核提供了一组最基本的服务,而其他服务都是通过接口连接到微内核上的,这需要微内核具有良好的可扩展性,并且可以简化对其他服务的开发。此外,微内核架构允许系统根据环境变化,方便灵活地增加、修改或删减其服务,从而提高系统的自适应性
解释器架构
解释器(Interpreter)架构用于仿真当前不具备的计算环境,例如在Windows操作系统中运行 Linux程序就需要运行虚拟机来仿真Linux环境。在解释器架构中,所有的程序都需要通过解释器的引擎进行解释才能执行,因此这些程序称为伪码程序。解释器通常包含4个组成部分:用来解释伪码程序的解释引擎、包含待解释程序的内存、解释引擎的控制状态,以及被仿真程序的当前状态
解释器架构的优点为:
1.可以使编写的程序独立于具体的运行环境,提高代码可移植性
2.可以仿真当前不具备的运行环境
解释器架构的缺点为:
1.对性能影响较大
2.解释器本身需要经过验证
基于规则的架构
基于规则(Rule-based)的架构是一种解释器架构风格,将人类专家的问题解决知识编码成规则,这些规则在系统执行的计算满足指定的条件时被执行或激活,通过不断地执行和激活规则,最终使得问题被解决,由于这些规则不能被计算机系统直接执行,因此需要通过解释器来解释它们
该架构和解释器架构各个部分的对应关系如下:
待执行的伪代码—-知识库
解释器引擎—-规则解释器
解释引擎的控制状态—-规则和数据元素选择器
运行于虚拟机中的被解释程序的当前状态—-工作内存
可以看出,基于规则的系统是解释器架构的一个实例,在业务逻辑复杂,需要使用规则推理来说实现应用系统中经常会用到
设计模式
架构风格是宏观的设计模式,描述高层组织的结构,关注整个系统的大局观和整体结构
设计模式描述的是低层局部的细节,是实现设计复用的有效手段,更专注于解决特定问题的方法和在代码层面的实现
设计模式按照作用分类有创造型、结构型、行为型
按作用域分类有类模式(静态关系)、对象模式(动态关系)
设计模式非常的多,由于篇幅限制,此处仅举几个例子
单例模式 (Singleton Pattern):
描述: 保证一个类只有一个实例,并提供一个全局访问点
应用场景: 当系统中需要一个全局唯一的对象来协调资源访问时,比如配置管理器或日志记录器。
工厂模式 (Factory Pattern):
描述: 定义一个接口用于创建对象,但让子类决定实例化哪个类。
应用场景: 当一个类无法预知它必须创建的类的对象时,可以使用工厂模式
观察者模式 (Observer Pattern):
描述: 定义一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都得到通知并被自动更新
应用场景: 当一个对象的状态变化需要通知其他对象,并且不希望这些对象耦合紧密时,可以使用观察者模式
适配器模式 (Adapter Pattern):
描述: 将一个类的接口转换成客户希望的另一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
应用场景: 当需要复用一些现存的类,但是它们的接口与你的代码不兼容时,可以使用适配器模式
装饰器模式 (Decorator Pattern):
描述: 动态地给一个对象添加一些额外的职责。就扩展功能而言,装饰器模式比生成子类更为灵活。
应用场景: 当需要动态地给一个对象添加额外的功能,而且这些功能可以组合时,可以使用装饰器模式
人机界面设计
界面设计原则
易学性(Learnability)
系统应容易学习和掌握,不应对用户有额外的知识和技能要求。
用户熟悉性(User familiarity)
界面应以用户导向的名称和观念为主,而不是以计算机的概念为主。这能让用户更快地熟悉系统,使用系统
一致性(Consistency)
系统的各个界面之间,甚至不同系统之间,应具有相似的界面外观、布局,相似的人机交互方式以及相似的信息显示格式等
减少意外(Minimal surprise)
系统功能和行为对用户应是明确、清楚的。例如:系统有标准的界面;系统不会产生异常的结果,在相同情况下总会有相同的行为;系统有预定的响应时间等
易恢复性(Recoverability)
系统设计应该能够对可能出现的错误进行检测和处理,提供机制允许用户从错误中恢复过来
提供用户指南(User guidance)
系统应提供及时的用户反馈和帮助功能
用户多样性(User diversity)
系统应适应各类用户(从偶然型用户、生疏型用户到熟练型用户,直至专家型用户)的使用需要,提供满足其要求的界面形式
黄金规则:
1.让用户驾驭软件,不是软件驾驭用户
2.减少用户的记忆
3.保持界面的一致性
人机交互方式
问答式对话
优点:容易使用、学习,软件编程实现容易,用户回答范围小,因此不易出错。
缺点:效率不高,速度慢,灵活性差,修改扩充不方便
直接操纵
示例:可视化编辑器、飞行控制系统和电视游戏等。
优点:直接操纵对新手很有吸引力,对知识断层的用户来说是容易记住的,可以快速地执行任务
菜单选择
优点:如果术语和菜单项的意义是可理解且明确的,则用户可以用少量的学习或记忆和很少的击键次数来完成任务
填表
在填表时,用户必须理解字段的标题,知道值的允许范围和数据输入方法,能够对出错信息做出反应对有知识断层的用户或经常性用户来说是最合适的
命令语言
对熟练型用户来说,命令语言提供了一个控制和创造性的氛围。用户学习句法并能够迅速地表达复杂的任务,而不必阅读容易分散注意力的提示信息。但这类界面出错率通常比较高,培训是必须的,保持性也比较差,很难提供出错信息和联机求助
自然语言
优点:具有用户无需学习训练就能以自然交流方式使用计算机的优点
缺点:具有输入冗长文字,自然语言语义有二义性,需要具有应用领域的知识基础以及编程实现困难等缺点。到目前为止,成功案例还比较少自然语言界面是最理想、最友好的人机界面类型,但是要变为现实,仍有很多工作要做
随着技术的变迁,还发展出了表情识别、实现操纵等新的交互方式
界面设计过程
用户界面的设计过程是迭代的,包括四个活动:用户分析 、界面设计 、界面原型开发 、界面评估
界面设计问题
在设计人机界面的过程中几乎总会遇到以下问题:响应时间、帮助设施、出错处理、菜单和命令交互、应用系统的可访问性和国际化。但是,许多设计者直到设计过程后期才注意到这些问题,这样做往往导致不必要的反复、项目延期及用户的沮丧感。最好的办法是在设计的初期就考虑这些问题,因为此时修改比较容易,代价也低
针对上述的前三个问题做具体阐述:
系统响应时间指从用户执行某个控制动作(如按回车键或点鼠标)到软件作出响应(期望的输出或动作)的时间
时间长度:系统响应时间长会使用户感到不安和沮丧。人的一般容忍度为15秒
可变性:稳定的响应时间(如1秒)比不定的响应时间(如0.1秒到2.5秒)要好。用户往往比较敏感,总是关心界面背后是否发生了异常
关于帮助设施,在设计时须考虑如下问题:在系统交互时,是否总能得到各种系统功能的帮助?是提供部分功能的帮助还是提供全部功能的帮助。
用户怎样请求帮助?使用帮助菜单、特殊功能键还是HELP命令。
怎样表示帮助?在另一个窗口中、指出参考某个文档(不是理想的方法)还是在屏幕特定位置的简单提示。
用户怎样回到正常的交互方式?可做的选择有:屏幕上显示返回键、功能键或控制序列。
怎样构造帮助信息?是平面式(所有信息均通过关键字来访问)、分层式(用户可以进一步查询得到更详细的信息)还是超文本式
交互系统给出的出错消息和警告应具备以下特征:
消息以用户可以理解的术语描述问题
消息应提供如何从错误中恢复的建议性意见
消息应指出错误可能导致哪些不良后果(比如破坏数),以便用户检查是否出现了这些情况或帮助用户进行改正
消息应伴随着视觉或听觉上的提示,也就是说,显示消息时应该伴随警告错误的颜色显示
消息应是“非批评性的”(nonjudgmental),即不能指责用户