11.1 面向对象编程 Object-Oriented Programming

面向对象编程意味着程序组织方式的改变。这个改变跟已经发生过的处理器运算处理能力分配的变化雷同。在 1970 年代,一个多用户的计算机系统代表着,一个或两个大型机连接到大量的哑终端(dumb terminal)。现在更可能的是大量相互通过网络连接的工作站 (workstation)。系统的运算处理能力现在分布至个体用户上,而不是集中在一台大型的计算机上。

面向对象编程所带来的变革与上例非常类似,前者打破了传统程序的组织方式。不再让单一的程序去操作那些数据,而是告诉数据自己该做什么,程序隐含在这些新的数据“对象”的交互过程之中。

举例来说,假设我们要算出一个二维图形的面积。一个办法是写一个单独的函数,让它检查其参数的类型,然后视类型做处理,如图 11.1 所示。

  1. (defstruct rectangle
  2. height width)
  3. (defstruct circle
  4. radius)
  5. (defun area (x)
  6. (cond ((rectangle-p x)
  7. (* (rectangle-height x) (rectangle-width x)))
  8. ((circle-p x)
  9. (* pi (expt (circle-radius x) 2)))))
  10. > (let ((r (make-rectangle)))
  11. (setf (rectangle-height r) 2
  12. (rectangle-width r) 3)
  13. (area r))
  14. 6

图 11.1: 使用结构及函数来计算面积

使用 CLOS 我们可以写出一个等效的程序,如图 11.2 所示。在面向对象模型里,我们的程序被拆成数个独一无二的方法,每个方法为某些特定类型的参数而生。图 11.2 中的两个方法,隐性地定义了一个与图 11.1 相似作用的 area 函数,当我们调用 area 时,Lisp 检查参数的类型,并调用相对应的方法。

  1. (defclass rectangle ()
  2. (height width))
  3. (defclass circle ()
  4. (radius))
  5. (defmethod area ((x rectangle))
  6. (* (slot-value x 'height) (slot-value x 'width)))
  7. (defmethod area ((x circle))
  8. (* pi (expt (slot-value x 'radius) 2)))
  9. > (let ((r (make-instance 'rectangle)))
  10. (setf (slot-value r 'height) 2
  11. (slot-value r 'width) 3)
  12. (area r))
  13. 6

图 11.2: 使用类型与方法来计算面积

通过这种方式,我们将函数拆成独一无二的方法,面向对象暗指继承 (inheritance) ── 槽(slot)与方法(method)皆有继承。在图 11.2 中,作为第二个参数传给 defclass 的空列表列出了所有基类。假设我们要定义一个新类,上色的圆形 (colored-circle),则上色的圆形有两个基类, coloredcircle

  1. (defclass colored ()
  2. (color))
  3. (defclass colored-circle (circle colored)
  4. ())

当我们创造 colored-circle 类的实例 (instance)时,我们会看到两个继承:

  1. colored-circle 的实例会有两个槽:从 circle 类继承而来的 radius 以及从 colored 类继承而来的 color
  2. 由于没有特别为 colored-circle 定义的 area 方法存在,若我们对 colored-circle 实例调用 area ,我们会获得替 circle 类所定义的 area 方法。

从实践层面来看,面向对象编程代表着以方法、类、实例以及继承来组织程序。为什么你会想这么组织程序?面向对象方法的主张之一说这样使得程序更容易改动。如果我们想要改变 ob 类对象所显示的方式,我们只需要改动 ob 类的 display 方法。如果我们希望创建一个新的类,大致上与 ob 相同,只有某些方面不同,我们可以创建一个 ob 类的子类。在这个子类里,我们仅改动我们想要的属性,其他所有的属性会从 ob 类默认继承得到。要是我们只是想让某个 ob 对象和其他的 ob 对象不一样,我们可以新建一个 ob 对象,直接修改这个对象的属性即可。若是当时的程序写的很讲究,我们甚至不需要看程序中其他的代码一眼,就可以完成种种的改动。 λ