软件设计基础
软件设计是将需求规格说明转化为具体的体系结构、模块划分、接口定义和数据结构描述的过程。
一、软件设计概述
1.1 设计阶段划分
| 阶段 | 别名 | 主要任务 |
|---|---|---|
| 概要设计 | 高层设计/体系结构设计 | 确定总体结构、划分模块、定义接口、选择技术方案 |
| 详细设计 | 低层设计/模块设计 | 对每个模块进行功能描述、算法设计、数据结构定义 |
1.2 设计过程内容
- 体系结构设计:确定系统整体框架
- 数据设计:定义数据结构和数据库
- 接口设计:定义模块间和系统外部接口
- 过程设计:设计算法和数据结构
二、软件设计基本原则
2.1 模块化 (Modularity)
定义:将复杂系统分解为若干个功能相对独立、规模适中的模块
优点:
- 降低系统复杂度
- 提高可理解性
- 提高可维护性
- 便于团队协作
2.2 信息隐蔽 (Information Hiding)
定义:将模块的内部实现细节对其他模块隐藏,只暴露有限的接口
优点:
- 减少模块间依赖
- 提高模块独立性
- 便于修改和维护
2.3 抽象 (Abstraction)
定义:关注事物的主要特征,忽略次要细节
层次:
- 过程抽象:关注功能,忽略实现
- 数据抽象:关注数据特性,忽略存储细节
三、内聚与耦合
3.1 内聚性 (Cohesion)
定义:模块内部各元素之间功能相关性的紧密程度
原则:追求高内聚
内聚类型(由低到高)
| 类型 | 说明 | 内聚程度 |
|---|---|---|
| 偶然内聚 | 模块内元素无实质联系,只是恰好放在一起 | 最低 |
| 逻辑内聚 | 元素逻辑相关但功能不同(如所有输入处理放一起) | ↓ |
| 时间内聚 | 元素在同一时间段执行(如初始化模块) | ↓ |
| 过程内聚 | 元素按特定顺序执行 | ↓ |
| 通信内聚 | 元素使用相同输入数据或产生相同输出 | ↓ |
| 顺序内聚 | 一个元素的输出作为另一个元素的输入 | ↓ |
| 功能内聚 | 所有元素共同完成一个单一、明确的功能 | 最高 |
记忆口诀
偶逻时过通顺功
低到高来记心中
功能内聚最理想
设计追求要牢记3.2 耦合性 (Coupling)
定义:模块与模块之间相互依赖的程度
原则:追求低耦合
耦合类型(由高到低)
| 类型 | 说明 | 耦合程度 |
|---|---|---|
| 内容耦合 | 一个模块直接访问或修改另一个模块的内部数据 | 最高(最差) |
| 公共耦合 | 多个模块共享全局数据 | ↓ |
| 外部耦合 | 模块共享外部设备或文件 | ↓ |
| 控制耦合 | 一个模块通过传递控制信息影响另一个模块的执行 | ↓ |
| 标记耦合 | 模块间传递记录信息(数据结构) | ↓ |
| 数据耦合 | 模块间只传递简单数据参数 | 最低(最好) |
记忆口诀
内公外控标数
高到低来排队
数据耦合最理想
内容耦合最要避3.3 内聚耦合综合
| 目标 | 原则 | 理想状态 |
|---|---|---|
| 内聚 | 尽量高 | 功能内聚 |
| 耦合 | 尽量低 | 数据耦合 |
黄金法则:高内聚、低耦合
四、SOLID设计原则
4.1 单一职责原则 (SRP)
Single Responsibility Principle
定义:一个类应该只有一个引起它变化的原因,即只承担一项职责
作用:
- 控制类的粒度
- 提高类的内聚性
- 降低复杂性
示例:
❌ 错误:User类同时处理用户数据和邮件发送
✅ 正确:User类只处理用户数据,EmailService类处理邮件发送4.2 开闭原则 (OCP)
Open/Closed Principle
定义:软件实体应该对扩展开放,对修改关闭
含义:
- 增加新功能时,通过添加新代码实现
- 不修改已有的、经过测试的稳定代码
实现方式:
- 使用抽象和多态
- 依赖抽象而非具体实现
4.3 里氏替换原则 (LSP)
Liskov Substitution Principle
定义:所有引用基类的地方必须能透明地使用其子类对象
简单理解:子类对象应该能够替换父类对象,且程序行为不变
违反示例:
❌ 正方形继承矩形,但设置宽高时行为不一致
✅ 使用接口或组合代替继承4.4 接口隔离原则 (ISP)
Interface Segregation Principle
定义:客户端不应该被迫依赖于它不使用的方法
原则:
- 接口要小而专一
- 避免"胖接口"
- 将大接口拆分成小接口
示例:
❌ 错误:IWorker接口包含work()和eat()方法,机器人也要实现eat()
✅ 正确:拆分为IWorkable和IFeedable两个接口4.5 依赖倒置原则 (DIP)
Dependency Inversion Principle
定义:
- 高层模块不应该依赖于低层模块,两者都应该依赖于抽象
- 抽象不应该依赖于细节,细节应该依赖于抽象
简单理解:面向接口编程,而不是面向实现编程
示例:
❌ 错误:OrderService直接依赖MySQLDatabase
✅ 正确:OrderService依赖IDatabase接口,MySQLDatabase实现该接口4.6 SOLID原则总结
| 原则 | 缩写 | 核心思想 |
|---|---|---|
| 单一职责 | SRP | 一个类只做一件事 |
| 开闭原则 | OCP | 对扩展开放,对修改关闭 |
| 里氏替换 | LSP | 子类可以替换父类 |
| 接口隔离 | ISP | 接口要小而专一 |
| 依赖倒置 | DIP | 依赖抽象而非具体 |
五、其他设计原则
5.1 迪米特法则 (LoD)
Law of Demeter / 最少知识原则
定义:一个对象应该对其他对象有尽可能少的了解
规则:
- 只与直接朋友通信
- 不要调用"朋友的朋友"的方法
朋友包括:
- 当前对象本身
- 当前对象的成员变量
- 方法参数
- 方法创建的对象
5.2 组合优于继承
定义:优先使用对象组合,而不是类继承来实现代码复用
原因:
- 继承是静态的,组合是动态的
- 继承破坏封装,组合保持封装
- 继承会导致类爆炸
六、考试要点
选择题常见考法
- 内聚类型判断:给定场景,判断属于哪种内聚类型
- 耦合类型判断:给定模块交互方式,判断耦合类型
- 设计原则应用:判断代码是否违反某设计原则
- 原则特点辨析:区分不同设计原则的含义
典型例题
题1:某模块内的各部分都是在同一时间段内执行,这种内聚属于? 答案:时间内聚
题2:模块A通过参数向模块B传递一个标志,模块B根据标志决定执行流程,这种耦合属于? 答案:控制耦合
题3:哪个原则要求"对扩展开放,对修改关闭"? 答案:开闭原则(OCP)
速记表
| 概念 | 理想状态 | 最差状态 |
|---|---|---|
| 内聚 | 功能内聚 | 偶然内聚 |
| 耦合 | 数据耦合 | 内容耦合 |
