使用**💡**标记的是期末考点!

软件需求的基本概念

软件设计概述

软件设计和软件需求

软件设计阶段解决的是软件开发如何去做的阶段。这是一个把软件需求转换为具体实施方案的阶段。这个阶段和软件需求各部分县关联的要点是:

  1. 围绕数据字典设计
  2. 研究分析数据流图
  3. 数据流在不同变换模块中转换
  4. 一句控制规格说明和状态转换图

    软件设计的任务

    概要设计

    概要设计就是把软件的整体进行一个设计,主要任务基于数据流图数据字典,确定系统的整体架构漫画风 软件体系结构的各个子系统或者模块。
    主要设计的内容如下:
  • 体系结构设计
  • 界面设计
  • 数据设计

    详细设计

    详细设计就是在概要设计的基础上,进一步实现各部分的细节,直到系统所有的内容都有详细的过程描述
    image.png

    💡软件设计的原则

    分而治之

    分而治之用于解决大型复杂度高的项目,其核心的思想就是把一个大问题转换为不同的小问题进行解决。模块化就是在软件设计上实现分而治之思想的技术手段。

    重用设计模式

    重用指的是对于同一事物不做修改或者稍作修改可以多次使用的机制。这一步一般出现在概要设计当中,考虑的软件开发整体应该如何设计,重用的内容就是软件设计模式

    设计模式越方便重用,那么开发越省时间和精力。

可跟踪性

软件设计的任务之一就是确定软件各部分之间的关系。设计系统结构就是要确定系统各部分和跟模块之间的关系,以便于在修改模块的时候,可以跟踪到模块相关的其他部分,便于正确的追溯问题的根源。

灵活性

灵活性指的是软件的设计需要便于修改。会发生修改的原因如下:

  • 用户需求发生改变
  • 设计存在缺陷
  • 设计需要进行优化
  • 设计利用重用

    一致性

    在软件设计中,界面视图的一致性保证了用户体验和对系统的忠诚度。不同人员集体完成同一软件项目,需要保持开发进度的一致性。

💡软件体系结构设计

体系结构设计概述

软件体系结构确定了系统的组织结构拓扑结构,显示了系统需求和构成系统的元素之间的对应关系,提供了一些设计决策的基本原理。
体系结构设计是软件设计的早期活动,它的作用集中在两点:

  • **硬件**:提供软件设计师能预期的体系结构描述。例如提起浏览器/服务器(B/S)模式,多层架构、数据库存储、客户端、逻辑服务器等一系列描述就展现在设计师的脑海里(后一页) 。
  • **软件**:数据结构、文件组织、文件结构体现了软件设计的早期抉择,这些抉择将极大地影响着后续的软件开发人员,影响着软件产品的最后成功。

    这一部分了解即可。

以数据为中心的数据仓库模型

基本概念

以数据为中心的数据仓库模型——数据仓库模型是能独立提供数据服务的封闭式数据环境。它不单独集成到某一应用系统中,而是为具体的应用系统提供服务。这些服务既有通用的公共服务,也有专门设计的领域服务。
image.png

简单来说就是把各项数据全部都集中都一起进行调用。

优点

  • 数据统一存储和管理,确保了数据的实时性。
  • 数据仓库对数据复杂性的统一封装,有利于数据共享。
  • 采用**黑板模型**,与某类数据有关的应用系统能及时获取数据。
  • 采用**数据订阅推送模型**,应用系统在有数据更新时,能动获得数据,而不用采取询问方式,这就提高了数据管理效率。
  • 各应用系统间仅通过数据仓库完成数据交换,在功能上没有关联,增加、删除应用系统及其部分功能,将不会影响其它应用系统的正确运行。

    缺点

  • 为了对数据仓库数据进行操作,不同应用系统的数据视图必须统一,否则难易达到数据共享的目的,但这不可避免的会降低各应用系统的效率。

  • 如果应用系统的数据结构发生改变,就需要单独设计数据适配器,以实现新的结构与数据仓库在数据上的匹配。这不仅增加应用系统设计的复杂度,而且有时甚至是难以完成这样的数据匹配。
  • 随着网络技术的发展,数据共享带来的访问控制的复杂性、安全性、效率、备份、存储、恢复策略等一系列问题,影响了仓库模型的有效利用。

    💡客户端/服务端的分布式结构

    基本概念

    在了解这个结构之前,首先需要了解两个概念:
    • **客户端**:或称为用户端,是指与服务器相对应,为客户提供本地服务的程序
    • **服务端**:是为客户端服务的。服务的内容诸如向客户端提供资源,保存客户端数据。

简单的来说,客户端用户展现内容,用户的请求逻辑由服务端完成。

举个例子,比如皮影戏,用户端就是观众看到的皮影,而服务端就是在后台对皮影进行操作。

常见的客户端有两种,一种是电脑上下载的软件或者手机上下载的APP,一种是Web客户端,也就是在浏览器中各个网页。前者和服务端构成的就是**C/S架构**后者和服务端构成的就是**B/S架构**
C/SB/S最大的区别就是:

对象 硬件环境 客户端要求 软件安装 升级和维护 安全性
C/S 用户固定,并且处于相同区域,要求拥有相同的操作系统。 客户端的计算机电脑配置要求较高。 每一个客户端都必须安装和配置软件 C/S每一个客户端都要升级程序。可以采用自动升级。 一般面向相对固定的用户群,程序更加注重流程,它可以对权限进行多层次校验,提供了更安全的存取模式,对信息安全的控制能力很强。一般高度机密的信息系统采用C/S结构适宜。
B/S 要有操作系统和浏览器,与操作系统平台无关。 客户端的计算机电脑配置要求较低。 可以在任何地方进行操作而不用安装任何专门的软件。 不必安装及维护

参见:
BS架构与CS架构的区别(最详细)_四九城小白~阿勋的博客-CSDN博客_cs结构

这里主要讲解的是C/S架构B/S架构C/S架构的一种扩展。在C/S架构中,客户端是发起请求的一方,服务端会响应客户端的请求并返回相应的内容。

举个例子:观众(客户端)我想要看《白蛇传》,后台的人接受到这个请求,开始操作表演《白蛇传》。

C/S架构的应用都由三个相对独立的逻辑部分组成:

  • 用户界面(类似皮影戏的幕布)
  • 用户逻辑(类似皮影戏背后的演员)
  • 数据访问(类似用于表演的影人)

    三层网络设计模式

    如图所示,就是三层网络设计模式。
    第三章:软件基础设计 - 图3
    (a)被称为**胖客户端模型**,也就是系统的逻辑处理部分全部集中在客户端,降低了服务器的压力,但是一旦改变,客户端就需要频繁的更新软件。(比如需要新增某一个功能)
    (c)被称为**瘦客户端模型**,和胖客户端模型相反,所有的逻辑处理部分都集中在服务端,这样做的好处是客户端就无需频繁的更新,只需要服务端更新了相关内容,客户端直接调用即可,但是坏处是服务端压力就比较大,同时增加了网络宽带的负载
    (b)就是前面两者的折中方式,既减轻了服务器的压力,又无需太过频繁的更新内容。

    特点

  1. 数据共享
  2. 异构性:客户端和服务端的配置可以不同
  3. 开放性:只要符合互联网协议,任何计算机等都可以接入互联网
  4. 易修改性:一般用户界面和业务处理逻辑都是分开编写的,相对独立,修改问题的时候就不用关系其他模块
  5. 透明性:在分布式架构当中,客户端只需要知道服务端的服务位置,后端的逻辑实现扥无需关心其架构和访问方式是怎么样的

    不足

  6. 复杂性高

  7. 安全性有待提升
  8. 运行状态难以确定

    层次模型

    层次模型是一种类似客户端/服务端模型的一种架构。这种模型把整个系统做了更进一步的细分,典型的层次模型如下:
    image.png
    网络中的两端进行网络对话的时候,不是从左端的应用层直接发送到右端的应用层,而是从左端的应用层开始,逐层向下传递,利用下层提供的服务对数据进行逐层封装,最后通过网络传输到右端的物理层,再向上传递给右层的应用层。

    这个模式适合增量开发

💡MVC模型

基本概念

MVC(Model-View-Controller)是“模型-视图-控制器”。它是一种软件设计模式,作用是把软件系统划分到模型视图控制器等三个子框架中去,使得软件逻辑部件可以有效划分,程序设计变得容易。目的是为了将软件系统各部件间的耦合度降至最低,以利于系统的测试和维护。
这里解释一下三个子框架:

  • 模型:指的是把系统应用的数据抽象成为模型,比如五子棋项目中,把五子棋抽象成为一个类,这就是模型。
  • 视图:指的是专门用于设计应用界面的模块,比如五子棋项目中对于棋子和棋盘的设计和绘制
  • 控制器:指的是逻辑控制部分,包括数据的访问(访问以往下过的棋局,胜率等),业务逻辑的处理(五子棋下棋逻辑,判输赢的逻辑等),页面的调用等部分

image.png
如上图所示,客户端主要和**控制器部分**进行交互,控制器会向客户端相应响应的视图和数据。

优势

  1. 一个模型可以对应多个不同的视图,可重用性好。
  2. 模型有自包含性,模型的所有的数据和逻辑操作都是在模型内部完成,不涉及视图的变化、控制器的判断。
  3. 控制层可以把一个模型和多个不同的视图组合在一起,完成多种类型的请求
  4. 利用系统的更新和升级,因为针对用户的修改需求,只需要修改其中相关的某一层次修改即可
  5. 利于工程化管理

    不足

  6. 增加了系统的复杂性

  7. 导致修改的连锁反应
  8. 数据访问效率低,视图不能直接访问数据,而是需要通过控制器

模块化设计

软件模块化和分解

什么是软件模块化

模块是程序语句的集合,它拥有独立的命名,明确的输入、输出和范围。程序设计中的函数过程等都可作为模块。模块用矩形框表示,并用模块名称来命名

模块化分解

image.png
简单来说就是把复杂问题进行逐步分解,一般来说分解过后,问题的复杂度和操作的工作量都会相对减少,但是过于将模块分解会适得其反。

💡抽象

抽象是指对软件设计不同层次的理解,如果说分解问题是对问题的细节进行分解,那么抽象则是直接忽略细节,直接寻找出问题的本质
百度百科上关于抽象的定义如下:

抽象是从众多的事物中抽取出共同的、本质性的特征,而舍弃其非本质的特征的过程。

举个简单的例子,在五子棋的项目工程中,我们把棋子定义为一个类,这个把具体的棋子转换为类的过程就是在抽象。
抽象的种类大致分为三种。

实体抽象

也叫对于数据的抽象,是对需求当中的实体进行抽象。比如简历自动获取系统当中的简历文件,很好理解。

接口抽象

接口抽象的目的是通过统一的接口,设计不同的实现过程。实现这一点最经典的例子就是ODBC数据连接。
下面举个例子一看就懂。
比如简历自动获取系统当中,我们需要简历数据库,而这个数据库可能有两个来源,一个是通过MySQL存储数据,一个是通过MongoDB存储数据。无论是哪一种方式存储,都需要最基本的四种操作:增删改查。下面以添加简历这个功能为例。
首先看一下两者的添加操作:

  1. // MySQL操作
  2. public class MySQLOperate {
  3. public String addResumByMySQL() {
  4. // 添加简历到MySQL数据库
  5. }
  6. }
  1. // MongoDB操作
  2. public class MongoDBOperate {
  3. public String addResumByMongoDB() {
  4. // 添加简历到MongoDBL数据库
  5. }
  6. }

在主函数当中调用添加简历方法,如说一开始使用的是MySQL数据库:

  1. public class Main{
  2. public static void main(String[] args) {
  3. MySQLOperate mySQLOperate = new MySQLOperate();
  4. mySQLOperate.addResumByMySQL();
  5. }
  6. }

现在用户需求改变,需要采用MongoDB数据,那么就要做如下修改:

  1. public class Main{
  2. public static void main(String[] args) {
  3. // MySQLOperate mySQLOperate = new MySQLOperate();
  4. // mySQLOperate.addResumByMySQL();
  5. MongoDBOperate mongoDBOperate = new MongoDBOperate();
  6. mongoDBOperate.addResumByMongoDB();
  7. }
  8. }

这里只是展示了一处改动,如果是多处改动,且需要频繁改动的情况,这种方法就很容易出错,对软件的维护很不利。但是无论是用MySQL还是MongoDB,所需要实现的方法都是一致的,目的都是一致的,所以不妨把这两个操作共有的方法抽象出一个共有的接口

  1. // 数据库操作
  2. public interface DataBaseOperate {
  3. public String addResum(); //注:接口中的方法只定义,不做任何实现。
  4. }

接下来分别让两个数据库相关的类继承实现这个接口:

  1. // MySQL操作
  2. public class MySQLOperate implements DataBaseOperate{
  3. @Override
  4. public String addResum() {
  5. // 添加简历到MySQL数据库
  6. }
  7. }
  1. // MongoDB操作
  2. public class MongoDBOperate implements DataBaseOperate{
  3. @Override
  4. public String addResum() {
  5. // 添加简历到MongoDBL数据库
  6. }
  7. }

接下来利用多态的特性,在Main中这么做实例化,假设一开始还是使用的MySQL数据库:

  1. public class Main{
  2. public static void main(String[] args) {
  3. DataBaseOperate dataBaseOperate = new MySQLOperate();
  4. dataBaseOperate.addResum();
  5. }
  6. }

如果需要改动,只需要修改实例化部分即可:

  1. public class Main{
  2. public static void main(String[] args) {
  3. // DataBaseOperate dataBaseOperate = new MySQLOperate();
  4. DataBaseOperate dataBaseOperate = new MongoDBOperate();
  5. dataBaseOperate.addResum();
  6. }
  7. }

面对后续再多的操作,也只需要修改这一处即可,这就是对接口抽象带来的好处。这样做的目的还可以让软件设计人员讲更多的精力投入到实现系统的业务逻辑当中。

设计模式抽象

设计模式抽象就是对设计模式进行抽象,对有相同数据组织、行为、结构的系统的指导性框架的抽象。

信息隐藏

信息隐藏是把数据结构与实现过程放在一起,使得相关内容彼此靠近,对外部提供给完整独立的功能,对隐藏信息的访问只能通过接口进行访问。这样做的目的是提高软件的可修改性和重用性
比如我们定义一个类,其中有一个列表:

  1. public class ArrayOperate{
  2. private List array = new ArrayList();
  3. public void addTwoNumber(Integer Number){
  4. array.add(Number);
  5. }
  6. }

如果我们在外部调用,因为array的修饰符是private,所以不能直接访问,只能通过addNumber方法对数组进行添加:

  1. public class Main{
  2. public static void main(String[] args) {
  3. ArrayOperate arrayOperate = new ArrayOperate();
  4. arrayOperate.addNumber(123);
  5. }
  6. }

如果我们不将信息隐藏:

  1. public class ArrayOperate{
  2. public List array = new ArrayList();
  3. public void addNumber(Integer Number){
  4. array.add(Number);
  5. }
  6. }

直接调用相应的属性:

  1. public class Main{
  2. public static void main(String[] args) {
  3. ArrayOperate arrayOperate = new ArrayOperate();
  4. arrayOperate.array.add(123);
  5. }
  6. }

这就出现了一个问题,就是内部细节的暴露,这样是不安全的,最简单的一个例子就是如果不去调用addNumber方法,而是直接获取array添加数据,可能添加的数据就是非法类型数据(比如我们这样就可以添加字符类型的**123**

  1. public class Main{
  2. public static void main(String[] args) {
  3. ArrayOperate arrayOperate = new ArrayOperate();
  4. arrayOperate.array.add('123'); // 传入非法数据,但是由于ArrayList包容所有的类型,所以编译器不会报错
  5. }
  6. }

做信息隐藏(也叫封装)的好处就是保护内部信息,不让外部的模块对其进行直接的修改,任何需要对内部信息增删改查的都需要通过其接口进行访问,这个接口就相当于一个检查站,决定外部的类能否访问/修改数据,如何访问/修改数据等。

💡模块独立性

是指软件系统中划分的模块完成一个相对独立的功能,而与其它模块的关联尽量只发生在接口上。因此,独立性是良好设计的关键,是衡量软件质量的重要指标之一。
有两个指标可以衡量模块的独立性:耦合性内聚性

内聚性

内聚性描述的是模块内部的紧密程度。内聚性越强,模块内部元素之间的关系越紧密,模块独立性越强。
image.png

耦合度

耦合度指的是模块之间的紧密程度。耦合度越低,模块之间的紧密程度月松散,模块的独立性越强。
image.png

简单的来说,软件设计的追求就是低耦合,高内聚

启发式规则

  1. 改进软件结构,提高软件模块独立性
  2. 模块规模适中
  3. 软件结构的宽度、深度、扇出度、扇入度都要适中
  4. 模块的作用域应在模块控制域之内
  5. 设计单入口、单出口的模块,力争降低模块接口的复杂度
  6. 模块功能可以预测