模块化与信息隐藏

结构化的模块化

耦合

定义:耦合描述的是两个模块之间的关系的复杂程度。根据其耦合性的高低可以依次分为:内容耦合、公共耦合、重复耦合、控制耦合、印记耦合、数据耦合。模块耦合性越高,模块的划分越差,越不利于软件的变更和复用。(下表的耦合性从高到低排列)

类型 解释 例子
内容耦合 一个模块直接修改或者依赖于另一个模块的内容 程序跳转GOTO;某些语言机制支持直接更改另一个模块的代码;改变另一个模块的内部数据
公共耦合 模块之间共享全局的数据 全局变量
重复耦合 模块之间有同样逻辑的重复代码 逻辑代码被复制到两个地方
控制耦合 一个模块给另一个模块传递控制信息 传递“显示星期天”。传递模块和接收模块必须共享一个共同的内部结构和逻辑
印记耦合 共享一个数据结构,但是却只用了其中一部分 传递了整个记录给另一个模块,另一个模块却只需要一个字段
数据耦合 两个模块的所有参数是同类型的数据项 传递一个整数给一个计算平方根的函数

没有耦合是最好的,但是对象之间的协作导致各个对象间肯定存在不同程度的耦合,我们要做的事尽量降低对象之间的耦合程度

四个原则

  1. 全局变量是有害的《Global Variables Consider Harmful》
  2. 显式声明成员变量《To be Explicit》
  3. 不要重复《Do not Repeat》
  4. 面向接口编程《Programming to Interface》

内聚

定义:内聚表达的是一个模块内部的联系和紧密性。内聚可以分为7个级别,由高到低包括信息内聚、功能内聚、通信内聚、过程内聚、时间内聚、逻辑内聚、偶然内聚。(下表按内聚的从低到高排列)

类型 解释 例子
偶然内聚 模块执行多个完全不相关的操作 把下列方法放在一个模块中:修车、烤面包、遛狗、看电影
逻辑内聚 模块执行一系列相关操作,每个操作的调用由其他模块来决定 把下列方法放在一个模块中:开车去、坐火车去、坐飞机去
时间内聚 模块执行一系列与时间有关的操作 把下列方法放在一个模块中:起床、刷牙、洗脸、吃早餐
过程内聚 模块执行一些与步骤顺序有关的操作 把下列方法放在一个模块中:守门员传球给后卫、后卫传球给中场球员、中场球员传球给前锋、前锋射门
通信内聚 模块执行一系列与步骤有关的操作,并且这些操作在相同的数据上进行 把下列方法放在一个模块中:查书的名字、查书的作者、查书的出版商
功能内聚 模块只执行一个操作或者达到一个单一的目的 下列内容都作为独立模块:计算平方根、决定最短路径、压缩数据
信息内聚 模块进行许多操作,各个都有各自的入口点,每个操作的代码相对独立,而且所有操作都在相同的数据结构上完成 比如数据结构中的栈,它包含相应的数据和操作。所有的操作都是针对相同的数据结构

思想的应用

低耦合处理

  • 软件体系结构的分层设计中:不同层的模块之间仅能通过程序调用与数据传递实现交互,不能共享数据(例如 Model 层建立一个数据对象并将引用传递给 Logic 曾使用),否则将导致公共耦合
  • 软件体系结构的逻辑包设计中:依据功能的特点将三个层次进一步划分为更小的包,而不是只使用 PresentationLogicModel 三个包,可以通过包分割实现接口最小化,这能去除不必要的耦合
  • 软件体系结构的物理包设计中:将不同包的重复内容独立为单独的包以消除重复,避免产生隐式的重复耦合
  • 详细设计中对象创建者的选择:如果两个对象A、B间已经有了比较高的耦合度了,那么使用A创建B或者反之就不会带来额外的耦合度 – 不增加新的耦合
  • 详细设计中选择控制风格:接触界面与逻辑对象的直接耦合

高内聚处理

  • 软件体系结构的分层设计中:三个层次都是高内聚的,一个交互处理任务,一个处理业务逻辑,一个处理数据持久化
  • 软件体系结构包设计中:将三个层次进一步划分为更小的包,可以实现每个更小的包都是高内聚的
  • 详细设计中抽象类的职责:要求状态与方法紧密联系就是为了达到高内聚(信息内聚)
  • 详细设计中使用控制风格:控制风格分离了控制逻辑,可以实现业务逻辑对象的高内聚(功能内聚)、因为封装了控制逻辑,所以控制器对象承载了不可避免的过程内聚、通信内聚和逻辑内聚,这就要求控制器对象必须是受控的,也是它们为什么倾向于对外委托而不是自己进行业务计算的原因。

结构化的信息隐藏

信息隐藏策略

  1. 按决策抽象的信息隐藏:每个模块都隐藏一个重要的设计决策。每个模块都承担一定的职责,对外表现为一份契约,并且在这份契约之下隐藏着只有这个模块知道的设计决策或者秘密,决策实现的细节只有该模块自己知道。抽象就是总结提炼本质特征,消除非本质的特征。只需要定义好各个模块的接口,就可以进行独立的开发,描述也相对简单
  2. 按算法分解的信息隐藏:必须先设计好所有的数据结构,建立复杂、准确的描述,然后才能并行开发。因为各个模块都共享数据结构。

第一种优于第二种[Parnas1972],第一种也是信息隐藏的基本思想

Module Guide

模块的主要秘密

主要秘密描述的是这个模块所要实现的用户需求。是设计者对用户需求的实现的一次职责分配。有了这个描述以后,我们可以利用它检查我们是否完成所有的用户需求,还可以利用它和需求优先级来决定开发的次序。

模块的次要秘密

次要秘密描述的是这个模块在实现职责时候所涉及的具体的实现细节。包括数据结构,算法,硬件平台等信息。

模块的角色

描述了独立的模块在整个系统中所承担的角色,所起的作用。以及与那些模块由相关联的关系。

模块的对外接口

模块提供给别的模块的接口。

思想的应用

  • 在软件体系结构设计的分层设计中:经验表明软件系统的界面是最经常变化的,其次是业务逻辑,最稳定的是业务数据。这就是分层⻛格建立 PrensentationLogicModel 三个层次的原因,它们体现了决策变化的划分类型,它们之间的依赖关系符合各自的稳定性。
  • 在软件体系结构设计的物理包设计中:
    1. 消除重复可以避免重复耦合,同时可以避免同一个设计决策出现在多个地方 – 这意味着该决策没有被真正隐藏(这也是控制耦合比数据耦合差的原因,因为没有将决策隐藏,而是交给了其他模块)
    2. 建立独立的安全包、通信包和数据库连接包,是为了封装各自的设计决策 – 安全处理、网络通信与数据库处理
  • 在软件体系结构设计与详细设计中:严格要求定义模块与类的接口,可以便利开发,更是为了实现信息隐藏。
  • 在详细设计中使用控制风格:专门用控制器对象封装关于业务逻辑的设计决策,而不是将其拆散分布到整个对象网络中去。

Reference

  1. 南京大学软件学院2022春季学期《软件工程与计算二》
文章作者: ZY
文章链接: https://zyinnju.com/2022/06/16/模块化与信息隐藏/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 ZY in NJU