7.2.5 类与模块化

我们在第 4 章讨论过模块化编程的思想。对于复杂程序,通常需要用分解的方法将程序 划分成若干模块,使每个模块仅针对有限的数据执行有限的操作。模块化能够使复杂程序的 设计更加可控。

对复杂程序一般有两种分解方法:功能分解和数据分解。功能分解是面向过程编程的基 础,依赖于子程序(如函数)概念,以过程为中心来建立功能模块;数据分解则是面向对象 编程的基础,依赖于类的概念,以数据为中心来建立数据模块。

功能模块不太适合复杂数据的处理。以处理“学生”数据的程序为例,如果按功能分解, 需要建立课程注册模块、修改学生信息模块、成绩登录模块等等。每一个模块(函数)的编 写,都需要知道“学生”数据的各种细节。

而数据模块则可以避免功能模块的不足。通过对“学生”的数据和操作的抽象,创建学 生类 S,对学生数据能够执行的操作构成 S 的对外界面,而操作的实现细节则隐藏在 S 内部, 从而使 S 的使用者无需了解“学生”数据的细节就能执行所需操作。

两种模块化方法具有类似的优点,如代码重用、易维护、支持团队开发等,但他们导致 的程序具有完全不同的执行方式。面向对象程序是由很多对象组成的,对象之间通过交互(发 送、接收消息)、协作完成计算任务,而传统程序则是由一系列预定的过程组成的,通过按顺序执行这些过程而完成计算任务。 模块化设计体现了信息隐藏的思想,即程序模块应当对模块用户尽可能隐藏内部细节,只保留必要的访问界面。对功能模块(函数),以 math 库中的函数 sqrt()为例,我们作为调用 者,并不知道该函数的内部实现细节,如数值的表示细节和求平方根的算法细节,而只需要 知道该函数能够对给定的数值求平方根即可。对数据模块(类)同样如此,以程序 7.6 定义 的 Projectile 类为例,该类的使用者无需了解炮弹究竟用什么数据来表示以及如何计算其飞 行,只需要了解该类的使用界面(update、getX、getY)就能编写炮弹模拟程序。

既然类是一种具有独立性的程序模块,就可以单独存储在模块文件中,无需与使用类的 代码(主程序)存储在一个程序文件中。这样做的好处是类模块可以重用,任何想使用这个 类的程序都可以导入类模块。例如,我们可以将 Projectile 类定义单独保存在模块 proj.py 中, 任何希望使用 Projectile 类的程序只需导入它,导入后即可创建对象、执行对象方法。就像下 面这样:

from proj import Projectile
def main():
    angle, vel, h0, time = getInputs() 
    cball = Projectile(angle, vel, h0) 
    while cball.getY() >= 0:
        cball.update(time)
    print "射程: %0.1f 米." % (cball.getX())

我们当然可以让每个类单独构成一个模块,但这样一来,当类的数目很多时会导致模块 数目过多,反而增加程序的复杂性。实际上我们通常是将若干个相关的类存储在一个模块文 件中,例如 5.4.2 节介绍的 graphics.py 模块中就包含了所有图形类。不过,使用类的程序一 般都放在与类模块不同的模块中。

很多面向对象编程语言都以“类库”的形式提供具有各种实用功能的类模块给程序员使 用,就像过去面向过程编程语言提供“函数库”一样。OOP 往往能非常简单地解决复杂问题, 因为专业的程序员已经开发了大量可重用的代码。