<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                在本章里,我們將使用 Lisp 來自己實現面向對象語言。這樣子的程序稱為嵌入式語言 (*embedded language*)。嵌入一個面向對象語言到 Lisp 里是一個絕佳的例子。同時作為一個 Lisp 的典型用途,並演示了面向對象的抽象是如何多自然地在 Lisp 基本的抽象上構建出來。 [TOC] ## 17.1 繼承 (Inheritance)[](http://acl.readthedocs.org/en/latest/zhCN/ch17-cn.html#inheritance "Permalink to this headline") 11.10 小節解釋過通用函數與消息傳遞的差別。 在消息傳遞模型里, 1. 對象有屬性, 2. 并回應消息, 3. 并從其父類繼承屬性與方法。 當然了,我們知道 CLOS 使用的是通用函數模型。但本章我們只對于寫一個迷你的對象系統 (minimal object system)感興趣,而不是一個可與 CLOS 匹敵的系統,所以我們將使用消息傳遞模型。 我們已經在 Lisp 里看過許多保存屬性集合的方法。一種可能的方法是使用哈希表來代表對象,并將屬性作為哈希表的條目保存。接著可以通過?`gethash`?來存取每個屬性: ~~~ (gethash 'color obj) ~~~ 由于函數是數據對象,我們也可以將函數作為屬性保存起來。這表示我們也可以有方法;要調用一個對象特定的方法,可以通過`funcall`?一下哈希表里的同名屬性: ~~~ (funcall (gethash 'move obj) obj 10) ~~~ 我們可以在這個概念上,定義一個 Smalltalk 風格的消息傳遞語法, ~~~ (defun tell (obj message &rest args) (apply (gethash message obj) obj args)) ~~~ 所以想要一個對象?`obj`?移動 10 單位,我們可以說: ~~~ (tell obj 'move 10) ~~~ 事實上,純 Lisp 唯一缺少的原料是繼承。我們可以通過定義一個遞歸版本的?`gethash`?來實現一個簡單版,如圖 17.1 。現在僅用共 8 行代碼,便實現了面向對象編程的 3 個基本元素。 ~~~ (defun rget (prop obj) (multiple-value-bind (val in) (gethash prop obj) (if in (values val in) (let ((par (gethash :parent obj))) (and par (rget prop par)))))) (defun tell (obj message &rest args) (apply (rget message obj) obj args)) ~~~ **圖 17.1:繼承** 讓我們用這段代碼,來試試本來的例子。我們創建兩個對象,其中一個對象是另一個的子類: ~~~ > (setf circle-class (make-hash-table) our-circle (make-hash-table) (gethash :parent our-circle) circle-class (gethash 'radius our-circle) 2) 2 ~~~ `circle-class`?對象會持有給所有圓形使用的?`area`?方法。它是接受一個參數的函數,該參數為傳來原始消息的對象: ~~~ > (setf (gethash 'area circle-class) #'(lambda (x) (* pi (expt (rget 'radius x) 2)))) #<Interpreted-Function BF1EF6> ~~~ 現在當我們詢問?`our-circle`?的面積時,會根據此類所定義的方法來計算。我們使用?`rget`?來讀取一個屬性,用?`tell`?來調用一個方法: ~~~ > (rget 'radius our-circle) 2 T > (tell our-circle 'area) 12.566370614359173 ~~~ 在開始改善這個程序之前,值得停下來想想我們到底做了什么。僅使用 8 行代碼,我們使純的、舊的、無 CLOS 的 Lisp ,轉變成一個面向對象語言。我們是怎么完成這項壯舉的?應該用了某種秘訣,才會僅用了 8 行代碼,就實現了面向對象編程。 的確有一個秘訣存在,但不是編程的奇技淫巧。這個秘訣是,Lisp 本來就是一個面向對象的語言了,甚至說,是種更通用的語言。我們需要做的事情,不過就是把本來就存在的抽象,再重新包裝一下。 ## 17.2 多重繼承 (Multiple Inheritance)[](http://acl.readthedocs.org/en/latest/zhCN/ch17-cn.html#multiple-inheritance "Permalink to this headline") 到目前為止我們只有單繼承 ── 一個對象只可以有一個父類。但可以通過使?`parent`?屬性變成一個列表來獲得多重繼承,并重新定義?`rget`?,如圖 17.2 所示。 在只有單繼承的情況下,當我們想要從對象取出某些屬性,只需要遞歸地延著祖先的方向往上找。如果對象本身沒有我們想要屬性的有關信息,可以檢視其父類,以此類推。有了多重繼承后,我們仍想要執行同樣的搜索,但這件簡單的事,卻被對象的祖先可形成一個圖,而不再是簡單的樹給復雜化了。不能只使用深度優先來搜索這個圖。有多個父類時,可以有如圖 17.3 所示的層級存在:?`a`?起源于?`b`?及?`c`?,而他們都是?`d`?的子孫。一個深度優先(或說高度優先)的遍歷結果會是?`a`?,?`b`?,?`d`,?`c`?,?`d`?。而如果我們想要的屬性在?`d`與?`c`?都有的話,我們會獲得存在?`d`?的值,而不是存在?`c`?的值。這違反了子類可覆寫父類提供缺省值的原則。 如果我們想要實現普遍的繼承概念,就不應該在檢查其子孫前,先檢查該對象。在這個情況下,適當的搜索順序會是?`a`?,?`b`?,?`c`?,?`d`?。那如何保證搜索總是先搜子孫呢?最簡單的方法是用一個對象,以及按正確優先順序排序的,由祖先所構成的列表。通過調用`traverse`?開始,建構一個列表,表示深度優先遍歷所遇到的對象。如果任一個對象有共享的父類,則列表中會有重復元素。如果僅保存最后出現的復本,會獲得一般由 CLOS 定義的優先級列表。(刪除所有除了最后一個之外的復本,根據 183 頁所描述的算法,規則三。)Common Lisp 函數?`delete-duplicates`?定義成如此作用的,所以我們只要在深度優先的基礎上調用它,我們就會得到正確的優先級列表。一旦優先級列表創建完成,?`rget`?根據需要的屬性搜索第一個符合的對象。 我們可以通過利用優先級列表的優點,舉例來說,一個愛國的無賴先是一個無賴,然后才是愛國者: ~~~ > (setf scoundrel (make-hash-table) patriot (make-hash-table) patriotic-scoundrel (make-hash-table) (gethash 'serves scoundrel) 'self (gethash 'serves patriot) 'country (gethash :parents patriotic-scoundrel) (list scoundrel patriot)) (#<Hash-Table C41C7E> #<Hash-Table C41F0E>) > (rget 'serves patriotic-scoundrel) SELF T ~~~ 到目前為止,我們有一個強大的程序,但極其丑陋且低效。在一個 Lisp 程序生命周期的第二階段,我們將這個初步框架提煉成有用的東西。 ## 17.3 定義對象 (Defining Objects)[](http://acl.readthedocs.org/en/latest/zhCN/ch17-cn.html#defining-objects "Permalink to this headline") 第一個我們需要改善的是,寫一個用來創建對象的函數。我們程序表示對象以及其父類的方式,不需要給用戶知道。如果我們定義一個函數來創建對象,用戶將能夠一個步驟就創建出一個對象,并指定其父類。我們可以在創建一個對象的同時,順道構造優先級列表,而不是在每次當我們需要找一個屬性或方法時,才花費龐大代價來重新構造。 如果我們要維護優先級列表,而不是在要用的時候再構造它們,我們需要處理列表會過時的可能性。我們的策略會是用一個列表來保存所有存在的對象,而無論何時當某些父類被改動時,重新給所有受影響的對象生成優先級列表。這代價是相當昂貴的,但由于查詢比重定義父類的可能性來得高許多,我們會省下許多時間。這個改變對我們的程序的靈活性沒有任何影響;我們只是將花費從頻繁的操作轉到不頻繁的操作。 圖 17.4 包含了新的代碼。?[λ](http://acl.readthedocs.org/en/latest/zhCN/notes-cn.html#notes-273)?全局的?`*objs*`?會是一個包含所有當前對象的列表。函數?`parents`?取出一個對象的父類;相反的?`(setfparents)`?不僅配置一個對象的父類,也調用?`make-precedence`?來重新構造任何需要變動的優先級列表。這些列表與之前一樣,由`precedence`?來構造。 用戶現在不用調用?`make-hash-table`?來創建對象,調用?`obj`?來取代,?`obj`?一步完成創建一個新對象及定義其父類。我們也重定義了`rget`?來利用保存優先級列表的好處。 ~~~ (defvar *objs* nil) (defun parents (obj) (gethash :parents obj)) (defun (setf parents) (val obj) (prog1 (setf (gethash :parents obj) val) (make-precedence obj))) (defun make-precedence (obj) (setf (gethash :preclist obj) (precedence obj)) (dolist (x *objs*) (if (member obj (gethash :preclist x)) (setf (gethash :preclist x) (precedence x))))) (defun obj (&rest parents) (let ((obj (make-hash-table))) (push obj *objs*) (setf (parents obj) parents) obj)) (defun rget (prop obj) (dolist (c (gethash :preclist obj)) (multiple-value-bind (val in) (gethash prop c) (if in (return (values val in)))))) ~~~ **圖 17.4:創建對象** ## 17.4 函數式語法 (Functional Syntax)[](http://acl.readthedocs.org/en/latest/zhCN/ch17-cn.html#functional-syntax "Permalink to this headline") 另一個可以改善的空間是消息調用的語法。?`tell`?本身是無謂的雜亂不堪,這也使得動詞在第三順位才出現,同時代表著我們的程序不再可以像一般 Lisp 前序表達式那樣閱讀: ~~~ (tell (tell obj 'find-owner) 'find-owner) ~~~ 我們可以使用圖 17.5 所定義的?`defprop`?宏,通過定義作為函數的屬性名稱來擺脫這種?`tell`?語法。若選擇性參數?`meth?`?為真的話,會將此屬性視為方法。不然會將屬性視為槽,而由?`rget`?所取回的值會直接返回。一旦我們定義了屬性作為槽或方法的名字, ~~~ (defmacro defprop (name &optional meth?) `(progn (defun ,name (obj &rest args) ,(if meth? `(run-methods obj ',name args) `(rget ',name obj))) (defun (setf ,name) (val obj) (setf (gethash ',name obj) val)))) (defun run-methods (obj name args) (let ((meth (rget name obj))) (if meth (apply meth obj args) (error "No ~A method for ~A." name obj)))) ~~~ **圖 17.5: 函數式語法** ~~~ (defprop find-owner t) ~~~ 我們就可以在函數調用里引用它,則我們的代碼讀起來將會再次回到 Lisp 本來那樣: ~~~ (find-owner (find-owner obj)) ~~~ 我們的前一個例子在某種程度上可讀性變得更高了: ~~~ > (progn (setf scoundrel (obj) patriot (obj) patriotic-scoundrel (obj scoundrel patriot)) (defprop serves) (setf (serves scoundrel) 'self (serves patriot) 'country) (serves patriotic-scoundrel)) SELF T ~~~ ## 17.5 定義方法 (Defining Methods)[](http://acl.readthedocs.org/en/latest/zhCN/ch17-cn.html#defining-methods "Permalink to this headline") 到目前為止,我們借由敘述如下的東西來定義一個方法: ~~~ (defprop area t) (setf circle-class (obj)) (setf (area circle-class) #'(lambda (c) (* pi (expt (radius c) 2)))) ~~~ ~~~ (defmacro defmeth (name obj parms &rest body) (let ((gobj (gensym))) `(let ((,gobj ,obj)) (setf (gethash ',name ,gobj) (labels ((next () (get-next ,gobj ',name))) #'(lambda ,parms ,@body)))))) (defun get-next (obj name) (some #'(lambda (x) (gethash name x)) (cdr (gethash :preclist obj)))) ~~~ **圖 17.6 定義方法。** 在一個方法里,我們可以通過給對象的?`:preclist`?的?`cdr`?獲得如內置?`call-next-method`?方法的效果。所以舉例來說,若我們想要定義一個特殊的圓形,這個圓形在返回面積的過程中印出某個東西,我們可以說: ~~~ (setf grumpt-circle (obj circle-class)) (setf (area grumpt-circle) #'(lambda (c) (format t "How dare you stereotype me!~%") (funcall (some #'(lambda (x) (gethash 'area x)) (cdr (gethash :preclist c))) c))) ~~~ 這里?`funcall`?等同于一個?`call-next-method`?調用,但他.. 圖 17.6 的?`defmeth`?宏提供了一個便捷方式來定義方法,并使得調用下個方法變得簡單。一個?`defmeth`?的調用會展開成一個?`setf`?表達式,但?`setf`?在一個?`labels`?表達式里定義了?`next`?作為取出下個方法的函數。這個函數與?`next-method-p`?類似(第 188 頁「譯註: 11.7 節」),但返回的是我們可以調用的東西,同時作為?`call-next-method`?。?[λ](http://acl.readthedocs.org/en/latest/zhCN/notes-cn.html#notes-273)?前述兩個方法可以被定義成: ~~~ (defmeth area circle-class (c) (* pi (expt (radius c) 2))) (defmeth area grumpy-circle (c) (format t "How dare you stereotype me!~%") (funcall (next) c)) ~~~ 順道一提,注意?`defmeth`?的定義也利用到了符號捕捉。方法的主體被插入至函數?`next`?是局部定義的一個上下文里。 ## 17.6 實例 (Instances)[](http://acl.readthedocs.org/en/latest/zhCN/ch17-cn.html#instances "Permalink to this headline") 到目前為止,我們還沒有將類別與實例做區別。我們使用了一個術語來表示兩者,*對象*(*object*)。將所有的對象視為一體是優雅且靈活的,但這非常沒效率。在許多面向對象應用里,繼承圖的底部會是復雜的。舉例來說,模擬一個交通情況,我們可能有少于十個對象來表示車子的種類,但會有上百個對象來表示特定的車子。由于后者會全部共享少數的優先級列表,創建它們是浪費時間的,并且浪費空間來保存它們。 圖 17.7 定義一個宏?`inst`?,用來創建實例。實例就像其他對象一樣(現在也可稱為類別),有區別的是只有一個父類且不需維護優先級列表。它們也沒有包含在列表?`*objs**`?里。在前述例子里,我們可以說: ~~~ (setf grumpy-circle (inst circle-class)) ~~~ 由于某些對象不再有優先級列表,函數?`rget`?以及?`get-next`?現在被重新定義,檢查這些對象的父類來取代。獲得的效率不用拿靈活性交換。我們可以對一個實例做任何我們可以給其它種對象做的事,包括創建一個實例以及重定義其父類。在后面的情況里,?`(setfparents)`?會有效地將對象轉換成一個“類別”。 ## 17.7 新的實現 (New Implementation)[](http://acl.readthedocs.org/en/latest/zhCN/ch17-cn.html#new-implementation "Permalink to this headline") 我們到目前為止所做的改善都是犧牲靈活性交換而來。在這個系統的開發后期,一個 Lisp 程序通常可以犧牲些許靈活性來獲得好處,這里也不例外。目前為止我們使用哈希表來表示所有的對象。這給我們帶來了超乎我們所需的靈活性,以及超乎我們所想的花費。在這個小節里,我們會重寫我們的程序,用簡單向量來表示對象。 ~~~ (defun inst (parent) (let ((obj (make-hash-table))) (setf (gethash :parents obj) parent) obj)) (defun rget (prop obj) (let ((prec (gethash :preclist obj))) (if prec (dolist (c prec) (multiple-value-bind (val in) (gethash prop c) (if in (return (values val in))))) (multiple-value-bind (val in) (gethash prop obj) (if in (values val in) (rget prop (gethash :parents obj))))))) (defun get-next (obj name) (let ((prec (gethash :preclist obj))) (if prec (some #'(lambda (x) (gethash name x)) (cdr prec)) (get-next (gethash obj :parents) name)))) ~~~ **圖 17.7: 定義實例** 這個改變意味著放棄動態定義新屬性的可能性。目前我們可通過引用任何對象,給它定義一個屬性。現在當一個類別被創建時,我們會需要給出一個列表,列出該類有的新屬性,而當實例被創建時,他們會恰好有他們所繼承的屬性。 在先前的實現里,類別與實例沒有實際區別。一個實例只是一個恰好有一個父類的類別。如果我們改動一個實例的父類,它就變成了一個類別。在新的實現里,類別與實例有實際區別;它使得將實例轉成類別不再可能。 在圖 17.8-17.10 的代碼是一個完整的新實現。圖片 17.8 給創建類別與實例定義了新的操作符。類別與實例用向量來表示。表示類別與實例的向量的前三個元素包含程序自身要用到的信息,而圖 17.8 的前三個宏是用來引用這些元素的: ~~~ (defmacro parents (v) `(svref ,v 0)) (defmacro layout (v) `(the simple-vector (svref ,v 1))) (defmacro preclist (v) `(svref ,v 2)) (defmacro class (&optional parents &rest props) `(class-fn (list ,@parents) ',props)) (defun class-fn (parents props) (let* ((all (union (inherit-props parents) props)) (obj (make-array (+ (length all) 3) :initial-element :nil))) (setf (parents obj) parents (layout obj) (coerce all 'simple-vector) (preclist obj) (precedence obj)) obj)) (defun inherit-props (classes) (delete-duplicates (mapcan #'(lambda (c) (nconc (coerce (layout c) 'list) (inherit-props (parents c)))) classes))) (defun precedence (obj) (labels ((traverse (x) (cons x (mapcan #'traverse (parents x))))) (delete-duplicates (traverse obj)))) (defun inst (parent) (let ((obj (copy-seq parent))) (setf (parents obj) parent (preclist obj) nil) (fill obj :nil :start 3) obj)) ~~~ **圖 17.8: 向量實現:創建** 1. `parents`?字段取代舊實現中,哈希表條目里?`:parents`?的位置。在一個類別里,?`parents`?會是一個列出父類的列表。在一個實例里,?`parents`?會是一個單一的父類。 2. `layout`?字段是一個包含屬性名字的向量,指出類別或實例的從第四個元素開始的設計 (layout)。 3. `preclist`?字段取代舊實現中,哈希表條目里?`:preclist`?的位置。它會是一個類別的優先級列表,實例的話就是一個空表。 因為這些操作符是宏,他們全都可以被?`setf`?的第一個參數使用(參考 10.6 節)。 `class`?宏用來創建類別。它接受一個含有其基類的選擇性列表,伴隨著零個或多個屬性名稱。它返回一個代表類別的對象。新的類別會同時有自己本身的屬性名,以及從所有基類繼承而來的屬性。 ~~~ > (setf *print-array* nil gemo-class (class nil area) circle-class (class (geom-class) radius)) #<Simple-Vector T 5 C6205E> ~~~ 這里我們創建了兩個類別:?`geom-class`?沒有基類,且只有一個屬性,?`area`?;?`circle-class`?是?`gemo-class`?的子類,并添加了一個屬性,?`radius`?。?[[1]](http://acl.readthedocs.org/en/latest/zhCN/ch17-cn.html#id8)?`circle-class`?類的設計 ~~~ > (coerce (layout circle-class) 'list) (AREA RADIUS) ~~~ 顯示了五個字段里,最后兩個的名稱。?[[2]](http://acl.readthedocs.org/en/latest/zhCN/ch17-cn.html#id9) `class`?宏只是一個?`class-fn`?的介面,而?`class-fn`?做了實際的工作。它調用?`inherit-props`?來匯整所有新對象的父類,匯整成一個列表,創建一個正確長度的向量,并適當地配置前三個字段。(?`preclist`?由?`precedence`?創建,本質上?`precedence`?沒什么改變。)類別余下的字段設置為?`:nil`?來指出它們尚未初始化。要檢視?`circle-class`?的?`area`?屬性,我們可以: ~~~ > (svref circle-class (+ (position 'area (layout circle-class)) 3)) :NIL ~~~ 稍后我們會定義存取函數來自動辦到這件事。 最后,函數?`inst`?用來創建實例。它不需要是一個宏,因為它僅接受一個參數: ~~~ > (setf our-circle (inst circle-class)) #<Simple-Vector T 5 C6464E> ~~~ 比較?`inst`?與?`class-fn`?是有益學習的,它們做了差不多的事。因為實例僅有一個父類,不需要決定它繼承什么屬性。實例可以僅拷貝其父類的設計。它也不需要構造一個優先級列表,因為實例沒有優先級列表。創建實例因此與創建類別比起來來得快許多,因為創建實例在多數應用里比創建類別更常見。 ~~~ (declaim (inline lookup (setf lookup))) (defun rget (prop obj next?) (let ((prec (preclist obj))) (if prec (dolist (c (if next? (cdr prec) prec) :nil) (let ((val (lookup prop c))) (unless (eq val :nil) (return val)))) (let ((val (lookup prop obj))) (if (eq val :nil) (rget prop (parents obj) nil) val))))) (defun lookup (prop obj) (let ((off (position prop (layout obj) :test #'eq))) (if off (svref obj (+ off 3)) :nil))) (defun (setf lookup) (val prop obj) (let ((off (position prop (layout obj) :test #'eq))) (if off (setf (svref obj (+ off 3)) val) (error "Can't set ~A of ~A." val obj)))) ~~~ **圖 17.9: 向量實現:存取** 現在我們可以創建所需的類別層級及實例,以及需要的函數來讀寫它們的屬性。圖 17.9 的第一個函數是?`rget`?的新定義。它的形狀與圖 17.7 的?`rget`?相似。條件式的兩個分支,分別處理類別與實例。 1. 若對象是一個類別,我們遍歷其優先級列表,直到我們找到一個對象,其中欲找的屬性不是?`:nil`?。如果沒有找到,返回?`:nil`。 2. 若對象是一個實例,我們直接查找屬性,并在沒找到時遞回地調用?`rget`?。 `rget`?與?`next?`?新的第三個參數稍后解釋。現在只要了解如果是?`nil`?,?`rget`?會像平常那樣工作。 函數?`lookup`?及其反相扮演著先前?`rget`?函數里?`gethash`?的角色。它們使用一個對象的?`layout`?,來取出或設置一個給定名稱的屬性。這條查詢是先前的一個復本: ~~~ > (lookup 'area circle-class) :NIL ~~~ 由于?`lookup`?的?`setf`?也定義了,我們可以給?`circle-class`?定義一個?`area`?方法,通過: ~~~ (setf (lookup 'area circle-class) #'(lambda (c) (* pi (expt (rget 'radius c nil) 2)))) ~~~ 在這個程序里,和先前的版本一樣,沒有特別區別出方法與槽。一個“方法”只是一個字段,里面有著一個函數。這將很快會被一個更方便的前端所隱藏起來。 ~~~ (declaim (inline run-methods)) (defmacro defprop (name &optional meth?) `(progn (defun ,name (obj &rest args) ,(if meth? `(run-methods obj ',name args) `(rget ',name obj nil))) (defun (setf ,name) (val obj) (setf (lookup ',name obj) val)))) (defun run-methods (obj name args) (let ((meth (rget name obj nil))) (if (not (eq meth :nil)) (apply meth obj args) (error "No ~A method for ~A." name obj)))) (defmacro defmeth (name obj parms &rest body) (let ((gobj (gensym))) `(let ((,gobj ,obj)) (defprop ,name t) (setf (lookup ',name ,gobj) (labels ((next () (rget ,gobj ',name t))) #'(lambda ,parms ,@body)))))) ~~~ **圖 17.10: 向量實現:宏介面** 圖 17.10 包含了新的實現的最后部分。這個代碼沒有給程序加入任何威力,但使程序更容易使用。宏?`defprop`?本質上沒有改變;現在僅調用?`lookup`?而不是?`gethash`?。與先前相同,它允許我們用函數式的語法來引用屬性: ~~~ > (defprop radius) (SETF RADIUS) > (radius our-circle) :NIL > (setf (radius our-circle) 2) 2 ~~~ 如果?`defprop`?的第二個選擇性參數為真的話,它展開成一個?`run-methods`?調用,基本上也沒什么改變。 最后,函數?`defmeth`?提供了一個便捷方式來定義方法。這個版本有三件新的事情:它隱含了?`defprop`?,它調用?`lookup`?而不是`gethash`?,且它調用?`regt`?而不是 278 頁的?`get-next`?(譯注: 圖 17.7 的?`get-next`?)來獲得下個方法。現在我們理解給?`rget`?添加額外參數的理由。它與?`get-next`?非常相似,我們同樣通過添加一個額外參數,在一個函數里實現。若這額外參數為真時,?`rget`?取代`get-next`?的位置。 現在我們可以達到先前方法定義所有的效果,但更加清晰: ~~~ (defmeth area circle-class (c) (* pi (expt (radius c) 2))) ~~~ 注意我們可以直接調用?`radius`?而無須調用?`rget`?,因為我們使用?`defprop`?將它定義成一個函數。因為隱含的?`defprop`?由?`defmeth`實現,我們也可以調用?`area`?來獲得?`our-circle`?的面積: ~~~ > (area our-circle) 12.566370614359173 ~~~ ## 17.8 分析 (Analysis)[](http://acl.readthedocs.org/en/latest/zhCN/ch17-cn.html#analysis "Permalink to this headline") 我們現在有了一個適合撰寫實際面向對象程序的嵌入式語言。它很簡單,但就大小來說相當強大。而在典型應用里,它也會是快速的。在一個典型的應用里,操作實例應比操作類別更常見。我們重新設計的重點在于如何使得操作實例的花費降低。 在我們的程序里,創建類別既慢且產生了許多垃圾。如果類別不是在速度為關鍵考量時創建,這還是可以接受的。會需要速度的是存取函數以及創建實例。這個程序里的沒有做編譯優化的存取函數大約與我們預期的一樣快。?[λ](http://acl.readthedocs.org/en/latest/zhCN/notes-cn.html#notes-284)?而創建實例也是如此。且兩個操作都沒有用到構造 (consing)。除了用來表達實例的向量例外。會自然的以為這應該是動態地配置才對。但我們甚至可以避免動態配置實例,如果我們使用像是 13.4 節所提出的策略。 我們的嵌入式語言是 Lisp 編程的一個典型例子。只不過是一個嵌入式語言就可以是一個例子了。但 Lisp 的特性是它如何從一個小的、受限版本的程序,進化成一個強大但低效的版本,最終演化成快速但稍微受限的版本。 Lisp 惡名昭彰的緩慢不是 Lisp 本身導致(Lisp 編譯器早在 1980 年代就可以產生出與 C 編譯器一樣快的代碼),而是由于許多程序員在第二個階段就放棄的事實。如同 Richard Gabriel 所寫的, > 要在 Lisp 撰寫出性能極差的程序相當簡單;而在 C 這幾乎是不可能的。?[λ](http://acl.readthedocs.org/en/latest/zhCN/notes-cn.html#notes-284-2) 這完全是一個真的論述,但也可以解讀為贊揚或貶低 Lisp 的論點: 1. 通過犧牲靈活性換取速度,你可以在 Lisp 里輕松地寫出程序;在 C 語言里,你沒有這個選擇。 2. 除非你優化你的 Lisp 代碼,不然要寫出緩慢的軟件根本易如反掌。 你的程序屬于哪一種解讀完全取決于你。但至少在開發初期,Lisp 使你有犧牲執行速度來換取時間的選擇。 有一件我們示例程序沒有做的很好的事是,它不是一個稱職的 CLOS 模型(除了可能沒有說明難以理解的?`call-next-method`?如何工作是件好事例外)。如大象般龐大的 CLOS 與這個如蚊子般微小的 70 行程序之間,存在多少的相似性呢?當然,這兩者的差別是出自于教育性,而不是探討有多相似。首先,這使我們理解到“面向對象”的廣度。我們的程序比任何被稱為是面向對象的都來得強大,而這只不過是 CLOS 的一小部分威力。 我們程序與 CLOS 不同的地方是,方法是屬于某個對象的。這個方法的概念使它們與對第一個參數做派發的函數相同。而當我們使用函數式語法來調用方法時,這看起來就跟 Lisp 的函數一樣。相反地,一個 CLOS 的通用函數,可以派發它的任何參數。一個通用函數的組件稱為方法,而若你將它們定義成只對第一個參數特化,你可以制造出它們是某個類或實例的方法的錯覺。但用面向對象編程的消息傳遞模型來思考 CLOS 最終只會使你困惑,因為 CLOS 凌駕在面向對象編程之上。 CLOS 的缺點之一是它太龐大了,并且 CLOS 費煞苦心的隱藏了面向對象編程,其實只不過是改寫 Lisp 的這個事實。本章的例子至少闡明了這一點。如果我們滿足于舊的消息傳遞模型,我們可以用一頁多一點的代碼來實現。面向對象編程不過是 Lisp 可以做的小事之一而已。更發人深省的問題是,Lisp 除此之外還能做些什么? 腳注 [[1]](http://acl.readthedocs.org/en/latest/zhCN/ch17-cn.html#id4) | 當類別被顯示時,?`*print-array*`?應當是?`nil`?。 任何類別的?`preclist`?的第一個元素都是類別本身,所以試圖顯示類別的內部結構會導致一個無限循環。 [[2]](http://acl.readthedocs.org/en/latest/zhCN/ch17-cn.html#id5) | 這個向量被 coerced 成一個列表,只是為了看看里面有什么。有了?`*print-array*`?被設成?`nil`?,一個向量的內容應該不會顯示出來。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看