11.1 面向对象编程 Object-Oriented Programming
面向对象编程意味着程序组织方式的改变。这个改变跟已经发生过的处理器运算处理能力分配的变化雷同。在 1970 年代,一个多用户的计算机系统代表着,一个或两个大型机连接到大量的哑终端(dumb terminal)。现在更可能的是大量相互通过网络连接的工作站 (workstation)。系统的运算处理能力现在分布至个体用户上,而不是集中在一台大型的计算机上。
面向对象编程所带来的变革与上例非常类似,前者打破了传统程序的组织方式。不再让单一的程序去操作那些数据,而是告诉数据自己该做什么,程序隐含在这些新的数据“对象”的交互过程之中。
举例来说,假设我们要算出一个二维图形的面积。一个办法是写一个单独的函数,让它检查其参数的类型,然后视类型做处理,如图 11.1 所示。
(defstruct rectangle
height width)
(defstruct circle
radius)
(defun area (x)
(cond ((rectangle-p x)
(* (rectangle-height x) (rectangle-width x)))
((circle-p x)
(* pi (expt (circle-radius x) 2)))))
> (let ((r (make-rectangle)))
(setf (rectangle-height r) 2
(rectangle-width r) 3)
(area r))
6
图 11.1: 使用结构及函数来计算面积
使用 CLOS 我们可以写出一个等效的程序,如图 11.2 所示。在面向对象模型里,我们的程序被拆成数个独一无二的方法,每个方法为某些特定类型的参数而生。图 11.2 中的两个方法,隐性地定义了一个与图 11.1 相似作用的 area
函数,当我们调用 area
时,Lisp 检查参数的类型,并调用相对应的方法。
(defclass rectangle ()
(height width))
(defclass circle ()
(radius))
(defmethod area ((x rectangle))
(* (slot-value x 'height) (slot-value x 'width)))
(defmethod area ((x circle))
(* pi (expt (slot-value x 'radius) 2)))
> (let ((r (make-instance 'rectangle)))
(setf (slot-value r 'height) 2
(slot-value r 'width) 3)
(area r))
6
图 11.2: 使用类型与方法来计算面积
通过这种方式,我们将函数拆成独一无二的方法,面向对象暗指继承 (inheritance) ── 槽(slot)与方法(method)皆有继承。在图 11.2 中,作为第二个参数传给 defclass
的空列表列出了所有基类。假设我们要定义一个新类,上色的圆形 (colored-circle),则上色的圆形有两个基类, colored
与 circle
:
(defclass colored ()
(color))
(defclass colored-circle (circle colored)
())
当我们创造 colored-circle
类的实例 (instance)时,我们会看到两个继承:
colored-circle
的实例会有两个槽:从circle
类继承而来的radius
以及从colored
类继承而来的color
。- 由于没有特别为
colored-circle
定义的area
方法存在,若我们对colored-circle
实例调用area
,我们会获得替circle
类所定义的area
方法。
从实践层面来看,面向对象编程代表着以方法、类、实例以及继承来组织程序。为什么你会想这么组织程序?面向对象方法的主张之一说这样使得程序更容易改动。如果我们想要改变 ob
类对象所显示的方式,我们只需要改动 ob
类的 display
方法。如果我们希望创建一个新的类,大致上与 ob
相同,只有某些方面不同,我们可以创建一个 ob
类的子类。在这个子类里,我们仅改动我们想要的属性,其他所有的属性会从 ob
类默认继承得到。要是我们只是想让某个 ob
对象和其他的 ob
对象不一样,我们可以新建一个 ob
对象,直接修改这个对象的属性即可。若是当时的程序写的很讲究,我们甚至不需要看程序中其他的代码一眼,就可以完成种种的改动。 λ