软件工程知识点总结(4):概要设计

1 设计的定义:软件设计解决的是“怎么做”的问题。软件设计是将需求描述的“做 什么”问题变为一个实施方案的创造性的过程。

概要设计从需求出发,从总体上描述系统架构以及应该包含的组成要素(模块), 同时描述各个模块之间的关联。

主要包括体系结构设计、构件(模块)设计、接口(界面)设计、数据设计 体系结构设计:确定架构模式。定义组成软件中各个主要的结构元素及它们之间 的联系的一个模型。

模块设计:将一个复杂系统按功能进行模块划分,建立模块的层次结构及调用关 系,确定模块间的接口及人机界面等。

接口设计:定义软件内部的通信、与系统的交互以及人机操作界面等。

数据设计:将实体 – 关系图中描述的对象和关系转化为数据结构的定义。

2 模块:是由边界元素限定的相邻程序元素的序列,而且有一个总体标识符代表它。

模块化:就是把程序划分成独立命名且可独立访问的模块,每个模块完成一个子 功能,把这些模块集成起来构成一个整体,可以完成指定的功能满足用户的需求。

模块化的优点:易于维护;易于分工合作;易于扩充功能;易于模块化测试。

3 设计遵循的原则之一——模块独立:

       希望这样设计软件结构,使得每个模块完成一个相对独立的特定子功能,并且和 其他模块之间的关系很简单。

模块独立程度的两个定性标准度量:

       耦合:如果改变程序中的一个模块,要求另一个模块也同时发生改变,就认为这 两个模块发生了耦合。衡量不同模块彼此间互相依赖(连接)的紧密程度。

       紧密:耦合高。不紧密:耦合低。

       内聚:衡量一个模块内部各个元素彼此结合的紧密程度。内聚要高,每个模块完 成一个相对独立的特定子功能。

耦合------程度的度量:

       (1) 非直接耦合/完全独立(no direct coupling):如果两个模块中的每一个都能独立地工作而不需要另一个模块的存在,那么它们 完全独立。

       在一个软件系统中不可能所有模块之间都没有任何连接。

       (2)数据耦合(data coupling):如果两个模块彼此间通过参数交换信息,而且交换的信息仅仅是数据,那么这种 耦合称为数据耦合。

Class A {
Test(B b){
Int i;
b.go(i);……. }
}
Class B{
go (int i){ …….. }
}

       评价:系统中至少必须存在这种耦合。一般说来,一个系统内可以只包含数据耦 合。数据耦合是理想的目标。维护更容易,对一个模块的修改不会使另一个模块 产生退化错误。

       (3) 控制耦合(control coupling):如果两个模块彼此间传递的信息中有控制信息,这种耦合称为控制耦合。

//对 user 表的处理:查询,删除。类**将控制信息 flag 传给了 DoUser,
并且能够控制 DoUser 的表现,类**和类 DoUser 就是控制耦合。
Void DoUser(int userId,int flag){
连接数据库;
If (flag==0){
查询 id 为 userId 的记录,并输出
}
Else if(flag==1){
删除 id 为 userId 的记录,输出是否删除成功
}
}

       评价:控制耦合往往是多余的,把模块适当分解之后通常可以用数据耦合代替它。 被调用的模块需知道调用模块的内部结构和逻辑,降低了重用的可能性。

       (4) 特征耦合(stamp coupling):当把整个数据结构作为参数传递而被调用的模块只需要使用其中一部分数据元 素时,就出现了特征耦合。

       示例:现有一个班的成绩信息表,包含学号,姓名,年龄,班级,籍贯等数据项。 要求编写函数对该班年龄进行修改。

#include "stdafx.h"
struct student{int sno;
char name[10];
char address[30];
char class[10];
int age;
}
int main(int argc, char* argv[])
{
struct student stu[50];
read(stu);
update(stu);
return 0;
}

       评价:被调用的模块可使用的数据多于它确实需要的 数据,这将导致对数据的访问失去控制,从而给计算 机犯罪提供了机会。无论何时把指针作为参数进行传 递,都应该仔细检查该耦合。

       (5) 公共环境耦合(common coupling) 一个模块往公共环境送数据,另一个模块从公共环境 取数据。即允许一组模块访问同一全局性的数据结构。 数据耦合的一种形式。是比较松散的耦合。如静态变量。

       (6) 内容耦合(content coupling):最高程度的耦合是内容耦合。如果出现下列情况之一,两个模块间就发生了内容

       耦合:一个模块访问另一个模块的内部数据;一个模块不通过正常入口转到另一 个模块的内部,如使用 goto;两个模块有一部分程序代码重叠;一个模块有多个 入口。

class A {
public int ma;
// .... }
class B {
private A a = new A();
public void methodA() {
a.ma += 1;
}
}

// 改正:
class A {
private int ma; // public --> private
// .... //加 getter/setter
public int getMa(){
return this.ma;
}
public void set(int ma) {
this.ma = ma;
}
}
class B {
private A a = new A();
public void methodA() {
this.a.setMa(this.a.getMa() + 1);
}
}

各种耦合对比:

内聚:标志一个模块内各个元素彼此结合的紧密程度,它是信息隐藏和局部化概 念的自然扩展。简单地说,理想内聚的模块只做一件事情。

要求:内聚有七类。设计时应该力求做到高内聚,通常中等程度的内聚也是可以 采用的,而且效果和高内聚相差不多;但是,低内聚不要使用。

       (1)偶然内聚(coincidental cohesion):如果一个模块的各成分之间毫无关系,则称为偶然内聚。

       例如:统计学生英语的平均成绩 和修改 id 为**的老师的基本信息 或者 发现一组语句在两处或多处出现,于是把这些语句作为一个模块以节省内存,也是偶然内聚。

       评价:模块内各元素之间没有实质性联系,很可能在一种应用场合需要修改这个 模块,在另一种应用场合又不允许这种修改,从而陷入困境;可理解性差,可维护性产生退化;模块是不可重用的。

       (2) 逻辑内聚(logical cohesion):如果一个模块完成的任务在逻辑上属于相同或相似的一类,则称为逻辑内聚。

       举例:一个子程序将打印季度开支报告、月份开支报告和日开支报告. 具体打印哪一个,将由传入的控制标志决定,这个子程序具有逻辑内聚性,因为它的内部逻辑是由输进去的外部控制标志决定的。

       评价:接口难以理解,造成整体上不易理解;完成多个操作的代码互相纠缠在一 起,局部功能的修改有时也会影响全局,导致严重的维护问题;难以重用。

       解决方案:模块分解。

       (3) 时间内聚(temporal cohesion)经典内聚:如果一个模块包含的任务必须在同一段时间内执行,就叫时间内聚。

       例如:初始化模块。初始化模块要为所有全局变量赋初值,对所有介质上的文件置初态,初始化寄存器和栈等,因此要求在程序开始执行的最初一段时间内,模 块中所有功能全部执行一遍。

       Windows 初始化: 初始化引导载入程序、操作系统选择、硬件检测、硬件配置文件。

       评价:时间关系在一定程度上反映了程序某些实质,所以时间内聚比逻辑内聚好一些。模块内操作之间的关系很弱,与其他模块的操作却有很强的关联。时间内 聚的模块不太可能重用。

       偶然内聚、逻辑内聚、时间内聚属于低内聚,不建议用

       (4) 过程内聚(procedural cohesion):如果一个模块内的处理元素是相关的,而且必须以特定次序执行,则称为过程内聚。

       这些操作原本并不需要关联到一起,只是因为人为的赋予特定的顺序。

       比如,银行接收用户信息模块,要求:必须是先输入身份证号(审核),后输入 姓名,然后是密码(密码)

       评价:比时间内聚好,至少操作之间是过程关联的。仍是弱连接,不太可能重用 模块。

       (5) 通信内聚(communicational cohesion) 如果模块中所有元素都使用同一个输入数据和(或)产生同一个输出数据,则称为 通信内聚。即在同一个数据结构上操作。

       如:更新某个数据时,先查找是否存在,然后再执行更新或插入操作。 (具体 操作可以通过调用函数实现)

       比如:统计数学成绩的平均成绩及 80 分以上人数

sum=0;
for(i=1;i<=50;i++){
sum=sum+maths[i];
if(maths[i]>=80)
count++;
} ………

       评价:模块中各操作紧密相连,比过程内聚更好。不能重用。

       解决方案:分成多个模块,每个模块执行一个操作。

       过程内聚、通信内聚属于中内聚,不建议使用

       (6) 顺序内聚(sequential cohesion):如果一个模块的各个成分和同一个功能密切相关,而且这些处理必须顺序执行 (一个成分的输出作为另一个成分的输入),则称为顺序内聚。

       例如某模块:完成工业产值求值的功能。第一个功能:求总产值,第二个功能: 求平均产值。显然该模块内两部分紧密关联。

       (7) 功能内聚(functional cohesion):如果模块内所有处理元素属于一个整体(每个处理都是必不可少),完成一个单一 的功能,则称为功能内聚。功能内聚是最高程度的内聚。

       评价:模块可重用,应尽可能重用;可隔离错误,维护更容易;扩充产品功能时 更容易。

       高内聚,低耦合的系统有什么好处呢?

       短期来看,并没有很明显的好处,甚至短期内会影响系统的开发进度,因为高内聚,低耦合的系统对开发设计人员提出了更高的要求。高内聚,低耦合的好处体现在系统持续发展的过程中,高内聚,低耦合的系统具有更好的重用性, 维护性,扩展性,可以更高效的完成系统的维护开发,持续的支持业务的发展, 而不会成为业务发展的障碍。

       高内聚:在模块划分时,要遵循“一个模块,一个功能”的原则,尽可能使模块达 到功能内聚           低耦合:通过分解软件系统降低耦合。

       分解的方法:分层和分割。但是在保持软件内在联系的前提下,如何分层分割系统,分层分割到什么样的粒 度,并不是一件容易的事,这方面有各种各样的分解方法。

4 典型的面向对象设计原则:

       Liskov 替换原则(LSP)、开放-封闭原则(OCP)、单一职责原则(SRP)、接口 隔离原则(ISP)、依赖倒置原则(DIP)

Liskov 替换原则(LSP):针对继承层次的设计,需要:

       “若对每个类型 S 的对象 O_1,都存在一个类型 T 的对象 O_2,使得在所有针对 T 编写的程序 P 中,用 O_1 替换 O_2 后,程序 P 的行为不变,则 S 是 T 的子类型” 

       理解:对于继承层次的设计,在任何情况下,子类与基类都是可以互换的,那么 该继承的使用就是合适的,否则可能会出问题。

       替换原则的另一种表达方式:

              子类不能添加任何基类没有的附加约 束。

              这些约束很可能造成使用者无法通 过子类正常的使用针对基类的程序。

       结论:实际操作中,基类往往就是抽象类(行为没有任何实现),甚至是接口。 引申:只要有可能,不要从具体类继承,而应该由抽象类继承或由接口实现。 ——“接口继承”。

开放-封闭原则(OCP):为了应对需求的变更,且同时保持相对稳定。设计时应满足:

       软件模块对于扩展是开放的:模块的行为可以扩展,当需求改变时,可以对模块替换原则的另一种表达方式:子类不能添加任何基类没有的附加约束。 这些约束很可能造成使用者无法通过类正常的使用针对基类的程序。 进行扩展,以满足新的需求。

       模块对于修改是封闭的:对模块行为扩展时,不必改动模块的源代码。

       该原则是面向对象设计中很多概念的核心!

单一职责原则(SRP):职责:要求某个类的对象所要履行的行为契约,它说明了该对象能够对外提供哪些行为,在设计中将演化为类的一个或多个操作。

       职责分两种类型:

              做(do)型职责:对象能够完成某些动作的职责。

              知道(know)行职责:对象提供自己所知道信息的职责。

       单一职责原则是指导类的职责分配的最基本原则。

       “对一个类而言,应该只有一类功能相关的职责”——单一职责原则

       满足内聚度最高的功能内聚

接口隔离原则(ISP):“使用多个专门的接口比使用单一的总接口要好”;更 具体的说,即一个类对另一个类的依赖性应当是建立在最小的接口上的。只提供 给客户端需要的办法。这种办法在服务行业中称为定制服务。ISP 使得接口的职 责明确,有利于系统的维护。

依赖倒置原则(DIP):传统的模块分层,最上层的模块通常都依赖下面的子模 块来实现。这种依赖层次,高层业务逻辑是建立在底层模块的基础上,底层模块 的修改将直接影响上层的各类应用模块。意味着很难得到有效的复用。

DIP 原则:“高层模块不应该依赖于底层模块,两者都应该依赖于抽象。”

       “抽象不应该依赖于细节,细节应依赖于抽象。”

       核心思想:“依赖止于抽象”

       由客户(即高层模块)来定义并公开接口,而底层去实现这些接口,即客户提出 他需要的服务,底层去实现这些服务。这样当底层实现逻辑发生变化时,高层模 块不受影响。

       由“依赖止于抽象”得到启发式规则:任何变量都不应该持有一个指向具体类的引 用;任何类都不应该从具体类派生;任何方法都不应该修改它的任何基类中的、 已经实现的方法

       应用分析:模拟人拨打手机

       一般思路:两个类:人(Person)和手机(Mobile)。人通过手机提供的 dial 方法拨打电话。

       按照 DIP 原则,需要为提供服务的 Mobile 建立抽象接口,从而消除它们之间的 直接依赖。           该接口 IMobile 代码如下:

       每一个具体的 Mobile 类都需要实现该接口。

       新的 Person 类代码:

       然后通过 xml 配置文件建立接口和具体类之间的依赖关系。

5 体系结构(架构)设计

架构是软件系统最本质的东西。是通过实践进而总结出的过往已验证优秀的设计 结构。往往能重复的使用到同领域中的其他项目中。如“房子”,架构就是房子的 结构和功能,可以延展到抗震性、防火性、防地表下陷性等。

良好的体系结构意味着普适、高效和稳定。

非常宏观的软件架构:C/S(客户机/服务器)、B/S(浏览器/服务器):演化出多 层架构,都是基于 MVC 架构、分布式架构、云架

架构与框架:

软件架构(体系结构):

       定义:是一系列相关的抽象模式,用于指导大型软件系统各个方面的设计,是一 个系统的草图。

       架构不是软件,而是关于软件如何设计的策略。是软件系统从整体到部分的最高层次的划分。它为软件系统提供了一个结构、行为和属性的高级抽象。

软件框架:

       为了实现某个软件组件规范时,提供规范所要求的基础功能的软件产品。

       是面向某种应用、可复用的“半成品”软件,由一组抽象类及其实例间协作关系 来表达。它实现最为基础的软件架构和体系,并提供了一些定义良好的可变点以 保证灵活性和可扩展性。

       框架的搭建需要架构方法论的指导。

       MVC 架构的一种应用- --SSM 框架 Spring + Spring

  • 11
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

茜茜西西CeCe

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值