11.9 封装 (Encapsulation)

面向对象的语言通常会提供某些手段,来区别对象的表示法以及它们给外在世界存取的介面。隐藏实现细节带来两个优点:你可以改变实现方式,而不影响对象对外的样子,而你可以保护对象在可能的危险方面被改动。隐藏细节有时候被称为封装 (encapsulated)。

虽然封装通常与面向对象编程相关联,但这两个概念其实是没相干的。你可以只拥有其一,而不需要另一个。我们已经在 108 页 (译注: 6.5 小节。)看过一个小规模的封装例子。函数 stampreset 通过共享一个计数器工作,但调用时我们不需要知道这个计数器,也保护我们不可直接修改它。

在 Common Lisp 里,包是标准的手段来区分公开及私有的信息。要限制某个东西的存取,我们将它放在另一个包里,并且针对外部介面,仅输出需要用的名字。

我们可以通过输出可被改动的名字,来封装一个槽,但不是槽的名字。举例来说,我们可以定义一个 counter 类别,以及相关的 incrementclear 方法如下:

  1. (defpackage "CTR"
  2. (:use "COMMON-LISP")
  3. (:export "COUNTER" "INCREMENT" "CLEAR"))
  4. (in-package ctr)
  5. (defclass counter () ((state :initform 0)))
  6. (defmethod increment ((c counter))
  7. (incf (slot-value c 'state)))
  8. (defmethod clear ((c counter))
  9. (setf (slot-value c 'state) 0))

在这个定义下,在包外部的代码只能够创造 counter 的实例,并调用 incrementclear 方法,但不能够存取 state

如果你想要更进一步区别类的内部及外部介面,并使其不可能存取一个槽所存的值,你也可以这么做。只要在你将所有需要引用它的代码定义完,将槽的名字 unintern:

  1. (unintern 'state)

则没有任何合法的、其它的办法,从任何包来引用到这个槽。 λ