09~12-详细设计

  • 详细设计的输入

    • 需求规格说明:分析类图、系统顺序图

    • 体系结构设计:接口

  • 详细设计的输出:设计类图

职责分配

  • 功能职责:方法

  • 数据职责:属性

  • 协作职责:与其他类的关系

    • 从小到大,将对象的小职责聚合成大职责

    • 从大到小,将大职责分配给每个小对象

GRASP 设计原则

  • 低耦合 Low-Coupling:减少模块之间的依赖关系

  • 高内聚 High-Cohesion:模块内部的元素之间的关系越紧密越好

  • 信息专家:拆分职责,职责分配给拥有所需信息的类

  • 创建者

  • 控制者

创建者

  • 以下优先级从高到低排列

场景
创建地点
创建时机
备注

组合关系

整体对象的创建定义和操作方法

整体对象的创建

例如,销售的业务逻辑对象由销售项对象创建

被某个对象记录和管理(单向关联)

关联对象的方法

业务方法的执行中对象的生命周期与此对象同步

连接池创建连接池对象

创建所需的数据被某个对象所持有

持有数据对象的方法

业务方法的执行中

也可由其他对象创建,由持有数据的对象初始化

聚合关系

整体对象的业务方法

业务方法的执行中

某个对象有多个关联时,优先选择聚合关联的整体对象,若有多个根据高内聚、低耦合的原则确定

其他

-

-

根据高内聚、低耦合原则确认

控制者

  • 目的:分配处理系统事件(外部事件)的职责

  • 解决方法:专门建立Controller类,作为中介分配事件处理的职责

  • 推荐的控制器候选类别(具体选择基于高内聚、低耦合原则)

    • 整个系统:SystemController

    • 某个业务/系统总体组织的类:OrderController

    • 在业务逻辑中“有行为”的角色:Cashier

    • 对于每个用例人为设计的控制器类:LoginHandler

类型
特点
优点
缺点

集中式

所有事件都在一个主控模块中处理(调用主控模块的方法)

设计简单,易于实现

复杂度高,耦合度高,难以维护

委托式

事件在主控模块中分发到其他模块处理(调用其他模块,由其他模块调用内部方法)

分散式

事件在数据和职责较少的模块间传输,完成处理

内聚度低带来高耦合,控制流复杂,模块散乱

画图

  • 类图:详见 UML 速查

  • 顺序图:和需求中的顺序图类似,需要将自然语言描述的方法转换为 实际的方法名称

耦合

  • 耦合:模块间关系的复杂程度

  • 以下分类从高到低排序

类型
定义
示例

内容耦合

模块直接访问另一模块的内部内容

直接访问内部变量、函数、GOTO跳入另一模块内部

公共耦合

模块通过公共变量共享数据

全局变量

重复耦合

模块间有重复逻辑的代码

代码复制

控制耦合

模块通过控制信息影响另一个模块的行为

传递标志位、状态等

印记耦合

共享数据结构,但是只使用了其中一部分

传递一个对象,但只使用了其中的一个属性

数据耦合

模块通过参数传递数据

函数参数

内聚

  • 内聚:衡量模块内部元素之间的相关性

  • 逻辑内聚和控制耦合常相关

  • 以下分类从低到高排序

类型
定义
示例

偶然内聚

模块执行完全无关的操作

修车、烤面包、遛狗

逻辑内聚

模块内操作相关(在逻辑、功能层面一致),但调用由其他模块决定

开车去、坐火车去

时间内聚

模块内操作和时间相关,本身不存在逻辑关系

起床、刷牙、洗脸、吃早餐

过程内聚

模块内的操作按特定顺序执行,存在逻辑关系

守门员传球给后卫,后卫传球给中场球员 ……

通信内聚

模块执行的操作在相同数据上进行

查书的作者、名字、出版商

功能内聚

模块内的操作共同完成单一功能

计算平方根

信息内聚

模块基于相同的数据结构,执行不同操作,各操作入口点、代码独立

面向对象的模块化

降低隐式耦合

  • 全局变量有害

  • 简洁

  • 避免代码重复

降低访问耦合

  • 访问耦合:链式调用,c = a().b()

  • 面向接口编程:主动设计需求接口

  • 迪米特法则:避免链式调用,只能出现一次.

    • O有方法m(),则O只能调用:O自身、O的成员变量、m的参数、m中局部变量的方法

  • 接口分离原则 ISP:接口应尽量独立、精简,避免实现者实现不必要的接口

降低继承耦合

  • 继承耦合:子类覆盖父类已有的代码

类型
定义

修改规格

子类修改父类的接口声明

修改实现

子类修改父类的实现,接口声明不变

精化规格

子类基于更严格的规则,修改父类的接口声明

精化实现

子类基于更严格的规则,修改父类的实现,接口声明不变

拓展

子类不对父类作更改,只增加新的方法、成员变量

两个类之间无继承关系

  • Liskov 替换规则 LSP:所有子类必须可以替换父类

  • 使用组合代替继承:可以在Stack中包含一个List,而不是继承自List

提高内聚

  • 单一职责原则 SRP:一个类只有一个引起变化的原因,每个类只负责单一功能

信息隐藏

  • 基本思想:模块隐藏重要设计决策的内部实现,只暴露必要的接口

  • 两种信息隐藏决策:按职责/算法分解,前者更好

设计原则

  • 封装

    • 封装数据和行为:部分属性无需暴露 Getter 和 Setter,不要暴露存储数据和推导数据 calculateAge

    • 封装内部结构:传递迭代器对象而非原来对象

    • 封装对象的引用:Getter 方法返回一个新的引用

    • 封装类型信息:使用父类的接口隐藏子类的类型信息

    • 封装可能存在的变更

  • 权限最小化原则

  • 开闭原则 OCP:对扩展开放,对修改关闭,添加新的代码而不需要修改原来的代码就能实现变更(善用多态)

  • 依赖倒置原则 DIP:高层模块不应该依赖低层模块,二者都应该依赖抽象(接口或抽象类),抽象不应该依赖细节,细节应该依赖抽象

    • 解决方案:为具体类建立接口

最后更新于