要点:
- 设计模式(PPT特别关注)ZJH
- 描述含义、如何实现、适用场合
- 模块化,结构化,高内聚低耦合的理解,为什么。耦合不可避免
- 模块化和成本的关系,PPT 关系图
- 模块化的 5 个标准
- 设计题要画图
- 设计模型、分析模型 HJ:
- 设计模型,面向对象设计中最关键的一步:确定类,如何确定?确定标准是什么?(PPT有关键字 + 课本上有详细)
软件设计
设计原则
开闭原则
理解:对服务方关闭修改提供扩展,对客户方允许修改。
解决方法:面向抽象编程,多使用接口和抽象类。
作用:实现高内聚低耦合。
开闭原则是最重要的设计原则,其他设计原则都是为了实现它而出现的。
里氏替换原则
理解:子类继承父类时,尽量复用父类已实现的方法而不是覆写。
里氏替换原则是开闭原则的重要实现方式之一。
解决方法:
- 使用接口实现、抽象类继承、组合聚合替代继承。
- 把两个不适合作为父子关系的类提升到同一级别,然后根据他两的共同点定义一个或多个更抽象的类或接口让它两共同继承或实现。
依赖倒置原则
理解:抽象不应该依赖细节,应该让细节依赖抽象。高层模块不应该依赖于底层模块,两者都应该依赖于抽象。
依赖倒置原则也是开闭原则的重要实现方式之一。
问题:高层模块 Copy 依赖于底层模块 ReadKeyboard 和 WritePrinter,当需要增加一个底层模块 WriteDisk 时,需要修改高层模块 Copy 的代码(红色部分),这违背了开闭原则。
解决:引入抽象层,让两者都依赖于抽象。
引入 Reader 和 Writer 抽象层,高层模块 Copy 和底层模块 ReadKeyboard, WritePrinter, WriteDisk 都依赖于抽象层,在 Copy 层编码时面向抽象编程,无需知道具体如何读,如何写。
此时无论底层要增加多少个具体写方法,具体读方法,高层模块都不需要修改,符合开闭原则。
接口分离原则
理解:当接口冗余时,对接口进行拆分,拆分为多个接口。
迪米特法则
- 含义:一个对象对另一个对象的实现细节保持最少的了解;尽量与直接朋友通信;
- 实现方法:属性私有,通过公共方法向外提供信息;多使用聚合、组合;
单一职责原则
- 含义:一个类只负责一项职责
- 实现方法:
模块化
把系统按照分解规则和设计规则拆分成一个个组件,降低系统设计的复杂度和组件之间的耦合度,实现组件之间的高内聚低耦合。
耦合是组件协作的前提,因此耦合是不可避免的。组件设计目标
- 把系统分解为组件。根据系统架构和设计原则把系统分解为许多组件。
- 描述组件的功能。正式或非正式地描述每个组件的功能。
- 辨识组件依赖。确定组件之间的依赖关系和通信机制。
- 明确组件接口。组件的接口定义应该方便组件测试和团队交流。
组件接口
- 为其他组件提供服务。
- 从其他组件获得服务。
- 访问权限控制。
模块化的优点
- 良好的扩展性。组件通过定义抽象接口向外提供服务,获取其他组件的服务。
- 良好的复用性。组件之间高内聚低耦合提升了组件的复用性。
- 良好的移植性。组件隐藏了依赖细节。
5个模块化评判标准
- 可分解性。大的组件可以分解为更小的组件。
- 可组合性。小的组件可以组合成更大的组件。
- 可理解性。组件功能可以被单独理解。
- 持续性。小的改变应该影响本地有限个其他组件。
- 保护性。组件运行异常时应该只影响少数其他组件并以预定的方式处理异常。
5个模块化规则
- 直接映射。保证解决方案和模块化兼容。
- 少接口。减少组件之间的通信。
- 小接口。常通信的两个组件应当减少通信量。
- 接口明显。互相通信的两个组件应该显式调用对方的接口。
- 信息隐藏。提高抽象程度,降低耦合。
模块化与成本
关系:
- 模块化程度越高,模块数越多,模块开发成本越低,模块集成成本越高,
- 软件开发成本 = 模块开发成本 + 模块集成成本。总体来说,随着模块数的增多,软件开发成本先减少后增大,因此需要找到一个临界点(临界模块数),获得最小的软件开发成本。
组件分析模型
分类
- 结构化分析。
- 面向对象分析。
4要素
1、基于场景的建模
Scenario-Based Modeling
2、基于类的建模
Class-Based Modeling
6 个类选择标准:
选择类必须符合以下标准
- 必要信息。潜在类必须记录必要的信息,在分析过程中发挥作用,才能保证系统正常工作。
- 所需服务。潜在类必须具有一组可确认的、能改变类的属性值的操作。
- 多个属性。单属性类的属性,在分析阶段可以作为另一个类的属性。
- 公共属性。适用于所有类的实例的属性。
- 公共操作。适用于所有类的实例的操作。
- 必要需求。类能满足必要的需求。
3、面向流的建模
Flow-Oriented Modeling
4、行为建模
Behavioral Modeling
组件设计模型
好的设计——用户角度
- 稳定性。程序不应该有阻碍功能正常执行的 BUG
- 适用性。程序应该适合它最初的目的。
- 令人愉悦。用户使用时应该是心情愉悦的。
组件设计流程
步骤 | 注意点 |
---|---|
1、找出类 | 优秀类的评判标准: - 完整且充分。封装了完整且充分的属性和方法。 - 原子性。类的方法够基本,一个方法只干一件事。 - 高内聚。一个类应该只干一种类型的事。 - 低耦合。类协作应该保持在可接受的最低范围内。 |
2、确定类协作关系 | - 指明协作的消息细节。 - 确定合适的接口。 - 确定交互的参数和数据结构。 - 确定工作流程的细节。 |
3、持久化数据 | - 描述持久化数据源,例如数据库、文件。 - 确定管理数据源的类。 |
4、阐述类行为 | - 对设计的类行为进行建模分析。 |
5、阐述部署 | - 描述最终如何部署系统的包、组件。 |
6、重设计 | - 考虑多几种设计方向。 - 不断全面化设计。 |
重构
含义:重构是指对一个软件系统,在不改变内部代码的行为的前提下,优化其内部结构。
作用:
- 优化软件的结构,适应新的变化。
- 去除冗余的、效率不高的代码,优化数据结构。
- 让软件更容易理解。
高内聚低耦合
高内聚和低耦合经常是一起出现的,它们都可以分为 3 个层次。
高内聚
核心:”single-mindedness”,单意识。
高内聚意味着,一个组件或一个类,只封装与它紧密相关的组件或类的属性和方法。
1、功能内聚
通常应用在方法层面。当模块只进行一次计算就要返回结果时。
把 displayID(), displayView(), displayZoom() 合成了一个 displayCamera(),只进行一次函数调用。
当要改变视野的显示方式时,只需要修改一个函数。
2、层次内聚
通常应用在包、组件和类层面。发生在高层可以访问底层,但是底层不能访问高层的时候。
3、通信内聚
访问相同数据的操作,都封装在一个类中。总的来说,这样的类只关注问题所需要的数据,访问并存储它们。
例如:对于一个 client 组件,其中的一个 StudentRecord 类应该包含增加、删除、更新和访问所有与学生记录有关的字段。StudentRecord
低耦合
耦合是用来描述两个类或组件之间的联系紧密度。
虽然追求低耦合,但是耦合是不可能避免的,因为耦合是协作的前提。
1、内容耦合
通常有两种情况:
- 一个组件偷偷摸摸地修改了另一个组件内部的数据。
- 违反信息隐藏规则。
两个方法在内容上耦合了。
2、公共耦合
通常出现在多个组件共同使用相同的全局变量。
都使用 setup 这个全局变量。
3、常规耦合
类型上的耦合在面向对象编程中是经常发生的。