14.1 模块化的驱动力:软件的推理

学习Java模块系统的各种细节之前,如果你能理解Java语言设计者设计的初衷和背景将会大有裨益。模块化意味着什么?模块系统能解决什么问题?本书花了大量的篇幅讨论新语言特性如何帮助程序员编写更接近问题描述的代码,以使代码更易于理解和维护。然而,这些都是底层的考虑。最终你需要从更高的层次(软件架构的层面)去设计,确保软件项目易于理解,进行代码变更时更加灵活、高效。接下来,我们会着重讨论两个设计模式,即关注点分离(separation of concern,SoC)和信息隐藏(information hiding),它们可以帮助创建易于理解的软件。

14.1.1 关注点分离

关注点分离推崇的原则是将单体的计算机程序分解为一个个相互独立的特性。譬如你要开发一个结算应用,它需要能解析各种格式的开销,能对结果进行分析,进而为顾客提供汇总报告。采用关注点分离,你可以将文件的解析、分析以及报告划分到名为模块的独立组成部分。模块是具备内聚特质的一组代码,它与其他模块代码之间很少有耦合。换句话说,通过模块组织类,可以帮助你清晰地表示应用程序中类与类之间的可见性关系。

你可能会质疑:“Java通过包机制不是已经对类进行了组织吗?为什么还需要模块?”你说得没错,不过Java 9的模块能提供粒度更细的控制,你可以设定哪个类能够访问哪个类,并且这种控制是编译期检查的。而Java的包并未从本质上支持模块化。

无论是从架构角度(比如,模型–视图–控制器模式)还是从底层实现方法(比如,业务逻辑与恢复机制的分离)而言,关注点分离都非常有价值。它能带来的好处包括:

  • 使得各项工作可以独立开展,减少了组件间的相互依赖,从而便于团队合作完成项目;
  • 有利于推动组件重用;
  • 系统整体的维护性更好。

14.1.2 信息隐藏

信息隐藏原则要求大家设计时尽量隐藏实现的细节。这一原则为什么非常重要呢?创建软件的过程中,我们经常遭遇需求变更的窘境。隐藏内部实现细节能帮你减少局部变更对程序其他部分的影响,从而有效地避免变更传递。换句话说,这是一种非常有用的代码管理和保护原则。我们经常听到封装这个词,意指一段代码的设计实现非常精巧,与应用的其他部分没有任何耦合,对这段代码内部实现的更迭不会对应用的其他部分产生影响。Java语言中,你可以通过private关键字,借助编译器验证组件中的类是否封装良好。不过,就语言层面而言,Java 9出现之前,编译器无法依据语言结构判断某个类或者包仅供某个特定目标访问。

14.1.3 Java软件

任何设计良好的软件都基于上述两个重要原则。那么,如何在Java语言中应用这两个原则呢?Java是一种面向对象的语言,我们日常打交道面对的都是类和接口。按照要解决的问题,对包、类以及接口代码进行分组,完成程序的模块化。实际操作时,以源代码方式展开分析可能过于抽象。你可以借助UML图这样的工具,以可视化的方式理解代码间的依赖。图14-1是一个UML图示例。这是一个管理用户注册信息的应用,它被分解成了三个独立的模块。

信息隐藏原则又该如何实现呢?你应该很熟悉Java语言的可见性描述符,它可以指定方法、字段以及类的访问控制,譬如:public、protected、包访问权限(package-level)或者是private。不过,正如下一节中将要提到的,这种方式提供的颗粒度很多情况下比较粗,即便你不希望用户能直接访问某个方法,可能还是不得不将其声明为public。在Java发展的早期,这并不是一个非常致命的问题,因为那时的应用规模比较小,依赖也相对简单。而现在,很多Java应用的规模都比较庞大,这个问题的严重程度日益凸显。事实上,如果你看到类中某个字段或者方法声明为public,就会下意识地觉得可以直接使用(难道不是吗?),然而这些方法设计者的初衷是它们只应该被他自己创建的有限类所访问!

14.1 模块化的驱动力:软件的推理 - 图1

图 14-1 三个独立的模块及它们之间的依赖

现在你应该已经理解模块化能带来的好处,甚至开始思考模块化对Java产生了哪些变化。接下来将围绕这一主题继续展开讨论。