Common Lisp 對象系統(tǒng),或稱 CLOS,是一組用來實現(xiàn)面向?qū)ο缶幊痰牟僮骷S捎谒鼈冇兄瑯拥臍v史,通常將這些操作視為一個群組。?λ?技術(shù)上來說,它們與其他部分的 Common Lisp 沒什么大不同:?defmethod
?和?defun
?一樣,都是整合在語言中的一個部分。
面向?qū)ο缶幊桃馕吨绦蚪M織方式的改變。這個改變跟已經(jīng)發(fā)生過的處理器運算處理能力分配的變化雷同。在 1970 年代,一個多用戶的計算機系統(tǒng)代表著,一個或兩個大型機連接到大量的啞終端(dumb terminal)?,F(xiàn)在更可能的是大量相互通過網(wǎng)絡(luò)連接的工作站 (workstation)。系統(tǒng)的運算處理能力現(xiàn)在分布至個體用戶上,而不是集中在一臺大型的計算機上。
面向?qū)ο缶幊趟鶐淼淖兏锱c上例非常類似,前者打破了傳統(tǒng)程序的組織方式。不再讓單一的程序去操作那些數(shù)據(jù),而是告訴數(shù)據(jù)自己該做什么,程序隱含在這些新的數(shù)據(jù)“對象”的交互過程之中。
舉例來說,假設(shè)我們要算出一個二維圖形的面積。一個辦法是寫一個單獨的函數(shù),讓它檢查其參數(shù)的類型,然后視類型做處理,如圖 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: 使用結(jié)構(gòu)及函數(shù)來計算面積
使用 CLOS 我們可以寫出一個等效的程序,如圖 11.2 所示。在面向?qū)ο竽P屠?,我們的程序被拆成?shù)個獨一無二的方法,每個方法為某些特定類型的參數(shù)而生。圖 11.2 中的兩個方法,隱性地定義了一個與圖 11.1 相似作用的?area
?函數(shù),當我們調(diào)用?area
?時,Lisp 檢查參數(shù)的類型,并調(diào)用相對應(yīng)的方法。
(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: 使用類型與方法來計算面積
通過這種方式,我們將函數(shù)拆成獨一無二的方法,面向?qū)ο蟀抵?em>繼承?(inheritance) ── 槽(slot)與方法(method)皆有繼承。在圖 11.2 中,作為第二個參數(shù)傳給?defclass
?的空列表列出了所有基類。假設(shè)我們要定義一個新類,上色的圓形 (colored-circle),則上色的圓形有兩個基類,?colored
?與?circle
?:
(defclass colored ()
(color))
(defclass colored-circle (circle colored)
())
當我們創(chuàng)造?colored-circle
?類的實例 (instance)時,我們會看到兩個繼承:
colored-circle
?的實例會有兩個槽:從?circle
?類繼承而來的?radius
?以及從?colored
?類繼承而來的?color
?。colored-circle
?定義的?area
?方法存在,若我們對?colored-circle
?實例調(diào)用?area
?,我們會獲得替?circle
?類所定義的?area
?方法。從實踐層面來看,面向?qū)ο缶幊檀碇苑椒?、類、實例以及繼承來組織程序。為什么你會想這么組織程序?面向?qū)ο蠓椒ǖ闹鲝堉徽f這樣使得程序更容易改動。如果我們想要改變?ob
?類對象所顯示的方式,我們只需要改動?ob
?類的?display
?方法。如果我們希望創(chuàng)建一個新的類,大致上與?ob
?相同,只有某些方面不同,我們可以創(chuàng)建一個?ob
?類的子類。在這個子類里,我們僅改動我們想要的屬性,其他所有的屬性會從?ob
?類默認繼承得到。要是我們只是想讓某個?ob
?對象和其他的?ob
?對象不一樣,我們可以新建一個?ob
?對象,直接修改這個對象的屬性即可。若是當時的程序?qū)懙暮苤v究,我們甚至不需要看程序中其他的代碼一眼,就可以完成種種的改動。?λ
在 4.6 節(jié)時,我們看過了創(chuàng)建結(jié)構(gòu)的兩個步驟:我們調(diào)用?defstruct
?來設(shè)計一個結(jié)構(gòu)的形式,接著通過一個像是?make-point
?這樣特定的函數(shù)來創(chuàng)建結(jié)構(gòu)。創(chuàng)建實例 (instances)同樣需要兩個類似的步驟。首先我們使用?defclass
?來定義一個類別 (Class):
(defclass circle ()
(radius center))
這個定義說明了?circle
?類別的實例會有兩個槽 (slot),分別名為?radius
?與?center
?(槽類比于結(jié)構(gòu)里的字段 「field」)。
要創(chuàng)建這個類的實例,我們調(diào)用通用的?make-instance
?函數(shù),而不是調(diào)用一個特定的函數(shù),傳入的第一個參數(shù)為類別名稱:
> (setf c (make-instance 'circle))
#<CIRCLE #XC27496>
要給這個實例的槽賦值,我們可以使用?setf
?搭配?slot-value
?:
> (setf (slot-value c 'radius) 1)
1
與結(jié)構(gòu)的字段類似,未初始化的槽的值是未定義的 (undefined)。
傳給?defclass
?的第三個參數(shù)必須是一個槽定義的列表。如上例所示,最簡單的槽定義是一個表示其名稱的符號。在一般情況下,一個槽定義可以是一個列表,第一個是槽的名稱,伴隨著一個或多個屬性 (property)。屬性像關(guān)鍵字參數(shù)那樣指定。
通過替一個槽定義一個訪問器 (accessor),我們隱式地定義了一個可以引用到槽的函數(shù),使我們不需要再調(diào)用?slot-value
?函數(shù)。如果我們?nèi)缦赂挛覀兊?circle
?類定義,
(defclass circle ()
((radius :accessor circle-radius)
(center :accessor circle-center)))
那我們能夠分別通過?circle-radius
?及?circle-center
?來引用槽:
> (setf c (make-instance 'circle))
#<CIRCLE #XC5C726>
> (setf (circle-radius c) 1)
1
> (circle-radius c)
1
通過指定一個?:writer
?或是一個?:reader
?,而不是?:accessor
?,我們可以獲得訪問器的寫入或讀取行為。
要指定一個槽的缺省值,我們可以給入一個?:initform
?參數(shù)。若我們想要在?make-instance
?調(diào)用期間就將槽初始化,我們可以用:initarg
?定義一個參數(shù)名。?[1]?加入剛剛所說的兩件事,現(xiàn)在我們的類定義變成:
(defclass circle ()
((radius :accessor circle-radius
:initarg :radius
:initform 1)
(center :accessor circle-center
:initarg :center
:initform (cons 0 0))))
現(xiàn)在當我們創(chuàng)建一個?circle
?類的實例時,我們可以使用關(guān)鍵字參數(shù)?:initarg
?給槽賦值,或是將槽的值設(shè)為?:initform
?所指定的缺省值。
> (setf c (make-instance 'circle :radius 3))
#<CIRCLE #XC2DE0E>
> (circle-radius c)
3
> (circle-center c)
(0 . 0)
注意?initarg
?的優(yōu)先級比?initform
?要高。
我們可以指定某些槽是共享的 ── 也就是每個產(chǎn)生出來的實例,共享槽的值都會是一樣的。我們通過聲明槽擁有?:allocation:class
?來辦到此事。(另一個辦法是讓一個槽有?:allocation?:instance
?,但由于這是缺省設(shè)置,不需要特別再聲明一次。)當我們在一個實例中,改變了共享槽的值,則其它實例共享槽也會獲得相同的值。所以我們會想要使用共享槽來保存所有實例都有的相同屬性。
舉例來說,假設(shè)我們想要模擬一群成人小報 (a flock of tabloids)的行為。(譯注:可以看看什么是 tabloids。)在我們的模擬中,我們想要能夠表示一個事實,也就是當一家小報采用一個頭條時,其它小報也會跟進的這個行為。我們可以通過讓所有的實例共享一個槽來實現(xiàn)。若?tabloid
?類別像下面這樣定義,
(defclass tabloid ()
((top-story :accessor tabloid-story
:allocation :class)))
那么如果我們創(chuàng)立兩家小報,無論一家的頭條是什么,另一家的頭條也會是一樣的:
> (setf daily-blab (make-instance 'tabloid)
unsolicited-mail (make-instance 'tabloid))
#<TABLOID #x302000EFE5BD>
> (setf (tabloid-story daily-blab) 'adultery-of-senator)
ADULTERY-OF-SENATOR
> (tabloid-story unsolicited-mail)
ADULTERY-OF-SENATOR
譯注: ADULTERY-OF-SENATOR 參議員的性丑聞。
若有給入?:documentation
?屬性的話,用來作為?slot
?的文檔字符串。通過指定一個?:type
?,你保證一個槽里只會有這種類型的元素。類型聲明會在 13.3 節(jié)講解。
defclass
?接受的第二個參數(shù)是一個列出其基類的列表。一個類別繼承了所有基類槽的聯(lián)集。所以要是我們將?screen-circle
?定義成circle
?與?graphic
?的子類,
(defclass graphic ()
((color :accessor graphic-color :initarg :color)
(visible :accessor graphic-visible :initarg :visible
:initform t)))
(defclass screen-circle (circle graphic) ())
則?screen-circle
?的實例會有四個槽,分別從兩個基類繼承而來。一個類別不需要自己創(chuàng)建任何新槽;?screen-circle
?的存在,只是為了提供一個可創(chuàng)建同時從?circle
?及?graphic
?繼承的實例。
訪問器及?:initargs
?參數(shù)可以用在?screen-circle
?的實例,就如同它們也可以用在?circle
?或?graphic
?類別那般:
> (graphic-color (make-instance 'screen-circle
:color 'red :radius 3))
RED
我們可以使每一個?screen-circle
?有某種缺省的顏色,通過在?defclass
?里替這個槽指定一個?:initform
?:
(defclass screen-circle (circle graphic)
((color :initform 'purple)))
現(xiàn)在?screen-circle
?的實例缺省會是紫色的:
> (graphic-color (make-instance 'screen-circle))
PURPLE
我們已經(jīng)看過類別是怎樣能有多個基類了。當一個實例的方法同時屬于這個實例所屬的幾個類時,Lisp 需要某種方式來決定要使用哪個方法。優(yōu)先級的重點在于確保這一切是以一種直觀的方式發(fā)生的。
每一個類別,都有一個優(yōu)先級列表:一個將自身及自身的基類從最具體到最不具體所排序的列表。在目前看過的例子中,優(yōu)先級還不是需要討論的議題,但在更大的程序里,它會是一個需要考慮的議題。
以下是一個更復(fù)雜的類別層級:
(defclass sculpture () (height width depth))
(defclass statue (sclpture) (subject))
(defclass metalwork () (metal-type))
(defclass casting (metalwork) ())
(defclass cast-statue (statue casting) ())
圖 11.3 包含了一個表示?cast-statue
?類別及其基類的網(wǎng)絡(luò)。
更多建議: