第五章:信息隐藏(和泄露)

信息隐藏

实现深层模块最重要的技术是信息隐藏。
基本思想是每个模块应封装一些知识,这些知识代表设计决策。该知识嵌入在模块的实现中,但不会出现在其接口中,因此其他模块不可见。

隐藏在模块中的信息通常包含有关如何实现某种机制的详细信息。
隐藏的信息:
包括与该机制有关的数据结构和算法
还可以包含较低级别的详细信息(例如页面大小)
还可以包含更抽象的教高级的概念,例如大多数文件较小的假设

信息隐藏在两个方面降低了复杂性。
首先,它将接口简化为模块。接口反映了模块更简单、更抽象的功能视图,并隐藏细节;这减少了使用该模块的开发人员的认知负担。
其次,信息隐藏使系统更容易演化。如果隐藏了一段信息,那么在包含该信息的模块之外就不存在对该信息的依赖,因此与该信息相关的设计更改只影响一个模块。

设计新模块时,应仔细考虑可以在该模块中隐藏哪些信息。
如果您可以隐藏更多信息,则还能简化模块的接口,这会使模块更深

通过声明变量和方法为私有来隐藏类中的变量和方法与信息隐藏不是同一回事
私有元素可以帮助隐藏信息,因为它们使无法从类外部直接访问项目。但是,有关私有的信息仍可以通过公共方法(如getter和setter方法)公开。发生这种情况时,变量的性质和用法就如同变量是公开的一样暴露。

信息隐藏的最佳形式是将信息完全隐藏在模块中,从而使该信息对模块的使用方无关且不可见。
但是,部分信息隐藏也具有价值。例如如果某个类的某些用户仅需要特定的功能或信息,并且可以通过单独的方法对其进行访问,以使其在最常见的用例中不可见,则该信息通常会被隐藏。与类的每个公开可见的信息相比,此类信息将创建更少的依赖项。

隐藏与其他模块无关的信息,可以减少依赖。
如果外部需要对某些信息进行访问或者更新,可以通过新增特定接口对其进行访问,从而减少其他模块对此模块的依赖

信息泄露

信息隐藏的反面是信息泄露。
如果一条信息反映在模块的接口中,则根据定义,该信息已经泄漏;
因此,更简单的接口往往与更好地信息隐藏相关。
但是,即使信息未出现在模块的接口中,也可能会泄漏信息。

即使两个类都不在接口中公开信息,但是实现取决于某字段值,如果该字段值发生了变化,那么两个类都需要修改。

信息泄漏是软件设计中最重要的危险信号之一。
作为软件设计师,你能学到的最好的技能之一就是对信息泄露的高度敏感性。
如果您在类之间遇到信息泄漏:
请自问“我如何才能重新组织这些类,使这些特定的知识只影响一个类?”。如果受影响的类相对较小,并且与泄漏的信息紧密相关,那么将它们合并到一个类中是有意义的。
另一个可能的方法是从所有受影响的类中提取信息,并创建一个只封装这些信息的新类。但是,这种方法只有您能够找到一个从细节中抽象出来的简单接口时才有效;如果新类通过其接口公开了大部分知识,那么它就不会提供太多的价值(您只是用通过接口的泄露替换了后门泄漏)

什么是后门泄漏

当在多个地方使用相同的知识时,例如两个都理解特定类型文件格式的不同类,就会发生信息泄漏。

时间分解

信息泄漏的一个常见原因是我称为时间分解的设计风格。
在时间分解中,系统的结构对应于操作将发生的时间顺序。
考虑一个应用程序...该应用程序可以分为三类:一类用于读取文件,另一类用于执行修改,第三类用于写出新版本。
很容易陷入时间分解的陷阱,因为在编写代码时通常会想到必须执行操作的顺序。但是,大多数设计决策会在应用程序的整个生命中的多个不同时刻表现出来。
结果,时间分解常常导致信息泄漏
文件读取和文件写入步骤都具有有关文件格式的知识,这会导致信息泄漏。 解决方案是将用于读写文件的核心机制结合到一个类中,在读取和写入阶段使用。

如果整个逻辑中按照时间发生的顺序将动作进行了拆分,可能第一步和第三步都需要用到某个知识,如果分散在两个地方,会导致无论之后知识变化了还是知识进化了,需要修改的都是两处地方,增加了系统的复杂性。

顺序通常很重要,因此它将反映在应用程序中的某个位置。
但是,除非该结构和信息隐藏保持一致,否则不应该将其反映在模块结构中。在设计模块时,应专注于执行每个任务所需的知识,而不是任务发生的顺序。

在时间分解中,执行顺序反映在代码结构中:在不同时间发生的操作在不同的方法或类中。如果在执行的不同点使用相同的知识,则会在多个地方对其进行编码,从而导致信息泄漏。

通过时间分解,确实将逻辑分解了,同时可能将同一个业务的逻辑分散在了各个方法中,导致整个模块复杂度增加。

只要有可能,类就应该“做正确的事”,而无需明确要求。默认值就是一个例子。

如果常用功能的API迫使用户了解很少使用的其他功能,这会增加不需要使用功能的用户的认知负担。

信息隐藏在类中

信息隐藏也可以应用于系统中的其他级别,例如类内。

尝试在一个类中设计私有方法,以便每个方法都封装一些信息或功能,并将其隐藏在类的其余部分中。

但是通过设计私有方法,封装了一些信息或功能,也可能陷入前面说的浅类或者浅方法的陷阱。理解方法的成本与新写逻辑的成本相近。

尽量减少每个使用实例变量的位置数量....如果可以减少使用变量的数量,则将消除类内的依赖关系并降低其复杂性

走得太远

仅当在其模块外部不需要隐藏的信息时,隐藏信息才有意义。如果模块外部需要该信息,则不得隐藏它。
作为软件设计师,您的目标应该是最大程度地减少模块外部所需的信息量。
重要的是要识别模块外部需要哪些信息,并确保将其公开。

结论

信息隐藏和深层模块密切相关。
如果模块隐藏了很多信息,则往往会增加模块提供的功能的数量,同时还会减少其接口数量。这使模块更深。
如果一个模块没有隐藏太多信息,则它要么功能不多,要么接口复杂。无论哪种方式,模块都是浅的。

将系统分解为模块时,请尽量不要受运行时操作顺序的影响。
请考虑执行应用程序任务时所需的不同知识块,并设计每个模块来封装一个或几个这些知识块。
这将产生带有深层模块的干净简单的设计。