热门问题
时间线
聊天
视角

Smalltalk

来自维基百科,自由的百科全书

Smalltalk
Remove ads

Smalltalk是一種動態型別反射式物件導向程式語言。Smalltalk由艾倫·凱、Dan Ingalls、Ted Kaehler、Adele Goldberg等於1970年代在全錄帕羅奧多研究中心開始開發。

快速預覽 編程範型, 設計者 ...
Remove ads

Smalltalk對其它眾多的程式設計語言的產生起到了極大的推動作用,特別是Objective-CCLOSPythonRuby等。1990年代湧現的許多軟體開發思想都得益於Smalltalk,例如設計模式敏捷編程代碼重構[6]等。

Remove ads

概述

Smalltalk和許多程式設計語言不同,它不僅僅是一門語言。下面從幾個不同的角度來解釋Smalltalk。

  • 一種物件導向的程式設計語言:它是一種物件導向的語言,包含語言的語法和語意。一些編譯器可以透過Smalltalk源程式產生可執行檔。這些編譯器通常產生一種能在虛擬機器上執行的二進制代碼。Smalltalk語言本身非常精煉。
  • 一種程式設計環境:這裡指的是一種提供許多物件的系統,而不是某種特殊的開發環境。和許多語言不同(包括C++),Smalltalk附帶有一個巨大的、相當標準的類別館。這些使得開發Smalltalk程式的效率非常高。在其它語言(例如AdaCPascal)中,通常被作為語言的一部分的功能(例如條件判斷,迴圈等),在Smalltalk由特定的類提供。
  • 一個應用開發環境(ADE):由於Smalltalk的歷史原因,它具有一個非常優秀的高度整合、開放的應用開發環境。由於開發環境中的瀏覽器、監視器以及除錯器,都由同樣的源程式衍生出來的,不同的版本之間也具有相當好的相容性。此外,這些工具的源程式都可以在ADE直接存取。
Remove ads

歷史

Thumb
Smalltalk-76
Thumb
VisualWorks英語VisualWorks,衍生於Smalltalk-80 v2的商業實現
Thumb
Pharo,從Squeak v3.9分叉出的衍生於Smalltalk-80 v1的開源實現

最早的Smalltalk原型由艾倫·凱於1970年代初提出。(來自Simula 67)、海龜繪圖(來自LOGO)以及圖形化使用者介面(來自Sketchpad等先驅系統)等概念的有機組合,構成了Smalltalk的最初的藍圖[5]

在1971年到1975年之間,艾倫·凱在Xerox PARC的小組,在Xerox Alto電腦上,設計並實現了第一個真正的Smalltalk語言系統,編譯器由Dan Ingalls負責主要實作。這個系統被稱為Smalltalk-71與Smalltalk-72,具有以下幾個技術創新:

  • 語言完全基於訊息交換Simula 67的類的概念。
  • 語言沒有固定的語法,語法分析由類本身完成。

開發環境的革新相當迅速。雖然當時的點陣圖顯示器十分昂貴,但是艾倫·凱卻說服了PARC,讓他使用這些點陣圖顯示器,這使得艾倫·凱和他的小組,能夠實現不同大小和字型的文字,使用多窗口環境,以及一些對圖像處理的高端支援。Smalltalk-72影響了演員模型的發展[7],它的語法和執行模型,與現代的Smalltalk變體有著顯著的差異。

在1975到1976年間,艾倫·凱小組認識到應當對執行效率和規模進行最佳化。於是他們在許多重要方面重新設計了Smalltalk系統,被稱為Smalltalk-76,它在語言上:

  • 引入了繼承和子類的概念[8]
  • 確定了語言的語法,這使得編譯器能夠產生高效、可執行、精煉的二進制代碼。
  • 拉里·泰斯勒設計了類瀏覽器,這極大地提高了Smalltalk程式設計師的編程效率。

前述的所有Smalltalk系統,都是在特殊的硬體上實現的,直到1977年至1978年,Bruce Horn和Ted Kaehler把Smalltalk-76移植到Xerox NoteTaker英語Xerox NoteTaker上,它是由Intel 8086處理器和自訂顯示器所組成的硬體環境。雖然這種硬體環境只生產了10台,但是它證明了在通常的處理器上實現Smalltalk的可能性。

在1979至1980年,部分受NoteTaker專案的影響,Smalltalk小組的注意力轉移到Smalltalk的銷售可行性上。小組設計並實現了新一代的Smalltalk系統,這次修改的目標著重於在標準硬體上的移植性等方面,被稱為Smalltalk-80,它包括:

  • 採取ASCII碼字元集,摒棄了原先在Smalltalk-72和Smalltalk-76中使用的特殊字元。
  • 取消了原始方法直接存取主記憶體的能力。取而代之的是引入一系列的原始方法提供相應的功能。
  • 引入了元類的概念[9]
  • 引入MVC(模型-視圖-控制器)系統以方便互動式應用軟體的開發。

Smalltalk-80是在PARC之外能獲得到的第一個語言變體,最初作為Smalltalk-80版本1,給與了少數公司(惠普蘋果公司泰克DEC)和大學(UC Berkeley),用於同行評審和在它們自己的平台上實現。後來在1983年普遍可獲得的實現,叫做Smalltalk-80版本2,發行為虛擬機器規定和映像(具有對象定義的獨立於平台的檔案)[10]

1988年Xerox PARC為了將Smalltalk推向市場而成立了分拆公司ParcPlace Systems。ANSI Smalltalk自從1998年來是標準的語言參考[11]

兩個當前流行的Smalltalk實現變體,是這些最初Smalltalk-80映像的後代。Squeak開源實現,它經由Apple Smalltalk[12],衍生自Smalltalk-80版本1.03[13]VisualWorks英語VisualWorks經由Smalltalk-80 2.5和ObjectWorks(二者都是ParcPlace Systems的產品),衍生自Smalltalk-80版本2[10]

Remove ads

物件導向程式設計

Thumb
Smalltalk-80例子代碼在Squeak下的類層級和與之並列的元類層級的示意圖。其中的rProtoObjectcClassmcMetaclass。藍色連接表示實例聯絡,綠色連接表示繼承聯絡。

如同其他物件導向語言,Smalltalk-80(而非Smalltalk-72)的中心概念是「對象」 。一個對象總是一個「」的一個「實例」。類是描述它們的實例的屬性和行為的「藍圖」。例如,一個GUI窗口類,可以聲明窗口擁有的屬性,比如標籤、位置和窗口是否可見。這個類還可以聲明其實例支援的操作,比如打開、關閉、移動和隱藏。每個特定窗口對象,對這些屬性都有自己的值,它們每個都能進行它的類別定義的操作。

Smalltalk對象確切的可以做三件事:

  1. 持有狀態(參照到其他對象)。
  2. 接收訊息自本身或其他對象。
  3. 在處理一個訊息的過程中,傳送訊息至本身或其他對象。

一個對象持有的狀態總是私有於這個對象。其他對象只能通過發動請求(訊息)至這個對象,來讓它做出查詢或變更這個狀態。任何訊息可以傳送給任何對象:當接收到一個訊息的時候,接收者確定這個訊息是否合適。Alan Kay評論說,儘管關注於對象,訊息才是Smalltalk中最重要的概念:「最大的想法是訊息傳遞,它是Smalltalk/Squeak核心的全部意義所在(它是我們在Xerox PARC階段從未真正完成的某種東西)。」[14]

不同於多數其他語言,Smalltalk對象可以在系統執行的同時進行修改。現場編碼和飛速應用修補程式,是Smalltalk的主導編程方法論,並且是它高效的主要原因。

Smalltalk是「純」物件導向程式設計語言,這意味著,不像C++Java,在作為對象的值和作為原始類型的值之間沒有區別。在Smalltalk中,原始值比如整數、布林值和字元,也是對象,這麼說的意義在於它們也是相應類別的實例,而且要傳送訊息來呼叫在它們上的運算。編程者可以通過子類,改變或擴充實現原始值的類,使得可以向它們的實例定義新行為,例如實現一個新的控制結構,甚至使得它們現有行為得以改變。這個事實被總結成常聽到的一句短語:「在Smalltalk中,所有東西都是對象」,它可以更精確的表達為:「所有的值都是對象」,因為變數不是。

因為所有的值都是對象,也是對象。每個類都是這個類的元類的一個實例。元類都是Metaclass(元類類)的實例,它也是對象,並且是Metaclass class(元類元類)的實例。代碼塊是Smalltalk表達匿名函式的方式,它也是對象[15]

Hello, World!例子

Hello, World!程式,實質上被所有電腦語言的課本用作要學習的第一個程式,它展示了這個語言的最基本語法和環境。對於Smalltalk,這個程式可極其簡單的書寫。下列代碼中,訊息show:被傳送給對象Transcript,具有字串文字'Hello, World!'作為它的實際參數。呼叫show:方法,導致它的實際參數,即字串文字'Hello, World!',顯示在叫做「副本」(Transcript)的終端窗口:

Transcript show: 'Hello, World!'.

注意需要打開Transcript窗口,來看到這個例子的結果。

語法

Smalltalk-80語法是相當極簡主義的,只基於了一小把的聲明和保留字。事實上,Smalltalk中只保留了六個「關鍵字」:truefalsenilselfsuperthisContext。它們的準確術語是「偽變數」,是服從變數識別碼規則的識別碼,但指示了編程者所不能變更的繫結。truefalsenil偽變數是單例實例。selfsuper,在回應一個訊息而啟用的方法中,指稱這個訊息的接收者;但是傳送給super的訊息,在這個方法的定義類的超類中尋找方法,而非這個接收者的類中,這允許子類中的方法呼叫在超類中的同名方法。thisContext指稱當前的活動記錄。

內建的語言構造只有訊息傳送、賦值、方法返回和某些對象的文字語法。從它最初作為給所有年齡兒童的語言開始,標準的Smalltalk語法以更像英語,而非主流編碼語言的方式使用標點符號。語言餘下部份,包括用於條件求值和迭代的控制結構,都由標準Smalltalk類別館實現在內建構造之上。出於效能上的原因,實現可以辨識並特殊處理某些這種訊息,但這只是最佳化而並未硬性規定入語言語法。

諺語「Smalltalk語法適合一張明信片」,所提及的是Ralph Johnson英語Ralph Johnson (computer scientist)的一個代碼片段,展示了一個方法的所有基本標準語法元素[16]

exampleWithNumber: x
  | y |
  true & false not & (nil isNil) ifFalse: [self halt].
  y := self size + super size.
  #($a #a 'a' 1 1.0)
    do: [ :each |
      Transcript 
        show: (each class name);
        show: (each printString);
        show: ' ' ].
  ^x < y
Remove ads

文字

下列例子詮釋了最常用的對象,可以在Smalltalk-80方法中被寫為文字英語Literal (computer programming)值。

數和字元

下列是數的某些可能例子:

42
-42
123.45
1.2345e2
2r10010010
16rA000

最後兩個專案分別是二進制和十六進制數。在r前的數是底數或基數。基數不必須是二的冪;例如36rSMALLTALK是一個有效的數值,等價於十進制的80738163270632

字元書寫時帶有前導的美元符:

$A

字串

字串是包圍在單引號內的字元序列:

'Hello, world!'

要在一個字串中包括一個引號,使用另一個引號來跳脫

'I said, ''Hello, world!'' to them.'

雙引號不需要跳脫,因為單引號界定字串:

'I said, "Hello, world!" to them.'

兩個相等的字串(字串相等,如果它們包含完全相同的字元)可以是駐留在主記憶體不同位置中的不同對象。

符號

除了字串,Smalltalk有一類叫做符號英語Symbol (programming)Symbol)的字元序列對象。符號保證是唯一的,沒有作為不同對象的兩個相等的符號。因此,符號非常易於比較,並經常用於語言構造中,比如用作訊息選擇子。

符號被寫為#跟隨著字串文字英語string literal。比如:

#'foo'

如果一個序列不包含空白或標點字元,還可以寫為:

#foo

陣列

例如定義了四個整數的一個陣列:

#(1 2 3 4)

很多實現支援下列位元組陣列(ByteArray)的文字語法,例如定義了四個整數的位元組陣列:

#[1 2 3 4]

其他

最後卻重要的是塊(匿名函式文字):

[... 一些smalltalk代码 ...]

很多Smalltalk方言為其他對象實現了額外的語法,但是上述的是所有方言都本質上支援的。

變數聲明

在各種Smalltalk中共同使用的有兩種變數:實例變數和臨時變數。其他變數和有關術語依賴於特定實現,例如VisualWorks英語VisualWorks有類共享變數和名字空間共享變數,而Squeak和很多其他實現,有類別變數、池變數和全域變數。

在Smalltalk中臨時變數聲明是在方法(見後)內聲明的變數。它們聲明在方法的頂部,作為由豎槓包圍的空格分隔的名字。例如:

| index |

聲明一個臨時變數名叫index,可以包含初始值nil

多個變數可以在一組豎槓內聲明:

| index vowels |

聲明了兩個變數:indexvowels。所有變數都要初始化。字串的索引變數,初始化為null字元或初始為0ByteArray,此外的所有變數初始化為nil

按命名約定,實例變數、臨時變數、方法或塊的參數,應當以小寫字母開頭,指示它們具有私有作用域,它們合稱為局部變數。而全域變數、類別變數、池字典、類名字,應當以大寫字母開頭,它們合稱為共享變數。

賦值

變數通過:=語法來指定一個值。比如:

vowels := 'aeiou'

指定字串'aeiou'至前面聲明的vowels變數。這個字串是個對象(在單引號之間的字元序列是文字字串的語法),在編譯時間由編譯器建立。

在最初的Parc Place映像中,現在底線(_)的字形,在那時是左向箭頭()字形(就像1963年版本ASCII代碼中那樣)。Smalltalk最初接受左向箭頭,作為唯一的賦值算符。一些現代代碼仍然包含充當賦值的底線,會讓人想起這種最初的用法。多數現代的Smalltalk實現接受要麼底線,要麼冒號等號語法。

訊息

訊息是Smalltalk中最基礎的語言構造。所有控制結構都實現為訊息傳送。Smalltalk預設的採用動態分派單一分派策略,這是相對於其他一些物件導向語言使用的多分派而言的。

一元訊息

下列例子是傳送訊息factorial至數值42

42 factorial

在這種情況下,42叫做這個訊息的「接收者」,而factorial是訊息的選擇子。接收者通過返回一個值來相應這個訊息(這個情形中是42的階乘)。同其他事物一樣,訊息的結果可以賦值給一個變數:

aRatherBigNumber := 42 factorial

上面的factorial是「一元」訊息,因為只涉及了一個對象,即接收者。

關鍵字訊息

訊息可以承載額外的對象作為實際參數,比如:

2 raisedTo: 4

在這個表達式中,涉及了兩個變數:2作為接收者而4作為訊息的實際參數。訊息結果,或用Smalltalk的說法,回答被認定為16。這種訊息叫做「關鍵字」訊息。訊息可以有多個實際參數,使用如下語法:

'hello world' indexOf: $o startingAt: 6

它的回答是在接收者字串中字元o的索引,從索引6開始尋找。這個訊息的選擇子是indexOf:startingAt:,構成自兩個部份或關鍵字。

這種關鍵字和實際參數的交織意圖改進代碼的可讀性,因為實際參數由前導於它們的關鍵字來解釋。例如,要建立一個矩形的表達式使用C++或Java類別語法可以寫為:

new Rectangle(100, 200);

不清楚這些實際參數分別是什麼。與之相反,在Smalltalk中,這個代碼可以寫為:

Rectangle width: 100 height: 200

這個情況下接收者是Rectangle類,回答是這個類的具有指定寬度和高度的一個實例。

二元訊息

最後,多數特殊(非字母)字元可以被用作所謂的「二元訊息」。這些允許了數學和邏輯算符以傳統形式書寫:

3 + 4

它傳送訊息+給接收者3,具有4作為實際參數傳遞(回答將是7)。類似的:

3 > 4

將訊息>傳送給3具有實際參數4(回答將是false)。

注意,Smalltalk-80語言自身,不包含著這些算符的含義。上述的結果,都只是這些訊息的接收者(這裡是數值實例),為了回應訊息+>而定義並返回的。這個機制的副作用是運算子多載,訊息>可以被其他對象所理解,允許使用形如a > b的表達式來比較它們。

表達式

一元訊息可以一個接一個的寫成方法鏈式呼叫

3 factorial factorial log

它傳送factorial3,接著傳送factorial到前面的結果6,接著傳送log到前面的結果720,產生最終的結果2.85733

一個表達式可以包括多次訊息傳送。在這個情況下,表達式依據一個簡單的優先級次序來分析。一元訊息有最高的優先級,隨後是二元訊息,最後是關鍵字訊息。例如:

3 factorial + 4 factorial between: 10 and: 100

被求值如下:

  1. 3接收訊息factorial並回答6
  2. 4接收訊息factorial並回答24
  3. 6接收訊息+具有24作為實際參數並回答30
  4. 30接收訊息between:and:具有10100作為實際參數並回答true

最後的訊息傳送的回答,是整個表達式的結果。

組合

在需要的時候使用圓括號可以改變求值的次序。例如:

(3 factorial + 4) factorial between: 10 and: 100

將改變表達式含義,首先計算3 factorial + 4產生10。接著10接收第二個factorial訊息,產生36288003628800接著接收between:and:,回答false

注意由於二元訊息的含義,不是硬性規定入Smalltalk-80語法的,它們全部都被認為有相等的優先級,並簡單的從左至右來求值。因此,使用二元訊息的Smalltalk表達式的含義,可能不同於傳統釋義:

3 + 4 * 5

被求值為(3 + 4) * 5,產生35。要得到預期回答23,必須使用圓括號來顯式的定義運算次序:

3 + (4 * 5)

複合

以點號分隔的表示式按順序執行。注意在變數定義和隨後的表達式之間沒有點號。一個表達式序列的值,是最後的表達式的值。除了最後的表達式之外,所有的表達式的值都被忽略。注意點號是分隔符而並非終結符,因此最終的點號是可選的。

下列(假想)例子中,書寫了一序列的表達式,每個都用點號分隔。這個例子首先建立類Window的一個新實例,儲存它在一個變數中,接著向它傳送兩個訊息:

| window |
window := Window new.
window label: 'Hello'.
window open

級聯

如果像上述例子這樣,將一序列訊息都傳送給相同的接收者,它們也可以寫為方法級聯呼叫,具有用分號分隔的單獨訊息:

Window new
  label: 'Hello';
  open

這種將前面例子的重新為一個單一表達式,避免了對將新窗口儲存在臨時變數的需要。依據平常的優先級規則,首先向Window類傳送一元訊息new,接著向new回答的那個對象,傳送label:open

可以使用yourself訊息來返回一個級聯訊息的接收者。

代碼塊

頭等對象。代碼塊即匿名函式,可以被表達一個文字值(它是一個對象,因為所有值都是對象)。這是通過方括號達成的:

[ :params | <消息表达式> ]

這裡的:params是代碼可以接受的形式參數的列表。結果的塊對象可以形成一個閉包:它可以在任何時間訪問它外圍的詞法作用域內的變數。這意味著下列Smalltalk代碼:

[:x | x + 1]

可以理解為:,或用λ演算表達為: :

塊可以通過傳送給它們value訊息來執行。塊有一個參數用value:,有2個參數使用value:value:,以此類推直到4個參數,對多於4個參數使用valueWithArguments:並將參數作為陣列傳遞。例如下面的表達式:

[:x | x + 1] value: 3

可以被求值為:,或用λ演算表達為:

塊返回(常稱為回答)其主體的最後一個表達式的值,除非有一個由顯式的^指示的返回,這時返回這個返回表達式的值。在塊內部的返回,充當了一種逃出(escape)機制。在一個巢狀的塊表達中的返回表達式,將終止在字面上包圍的方法。

塊的文字表示是一種創新,它一方面允許特定代碼有更重大的可讀性;它允許涉及迭代的演算法一更清晰和簡潔的方式編碼。典型的在某些語言中使用迴圈寫成的代碼,可以在Smalltalk中使用塊簡潔的書寫,有時在單一一行之內。更加重要的,塊允許使用訊息和多型來表達控制結構,因為塊推延了計算,而多型可以用來選擇交替者(alternative)。所以在Smalltalk 80中,if…then…else被書寫和實現為:

expr ifTrue: [ expr为真时求值的语句 ] ifFalse: [ expr为假时求值的语句 ]

再舉一例,向一個搜集傳送訊息select:

positiveAmounts := allAmounts select: [:anAmount | anAmount isPositive]

注意這與函數式程式設計有關,這裡的計算模式被抽象成了高階函式select:等價於在一個適當的函子上的高階函式filter[17]

控制結構

在Smalltalk中控制結構沒有特殊的語法。它們轉而實現為傳送到對象上的訊息。以條件執行為例,布林類Boolean定義了ifTrue:ifFalse:ifTrue:ifFalse:ifFalse:ifTrue:方法。比如向一個布林對象,傳送ifTrue:訊息,並傳遞一個代碼塊作為實際參數,這個塊被執行若且唯若布林接收者為真。下面用一個例子來展示:

result := a > b
  ifTrue: [ 'greater' ]
  ifFalse: [ 'less or equal' ]

塊也被用來實現,使用者定義控制結構、列舉元訪問者例外處理、可插拔的行為和很多其他模式。

迭代

下面例子,從一個字串中過濾出其中所含有的元音字元:

| aString vowels |
aString := 'This is a string'.
vowels := aString select: [:aCharacter | aCharacter isVowel].

在最後一行,向字串對象aString傳送一個select:訊息,它具有一個代碼塊[:aCharacter | aCharacter isVowel]作為實際參數。這個代碼塊,表示一個測試,代碼塊文字將被用作一個謂詞函式,它回答true,若且唯若這個字串的一個元素aCharacter,應當被包括在滿足這個測試的字元搜集之中。

字串類String回應select:訊息,要呼叫的select:方法,定義並實現在搜集類Collection[18];它將給select:的實際參數選擇塊,傳送給形式參數aBlock;然後將繫結了選擇塊的aBlock嵌入到迭代塊的代碼之中,再把這個迭代塊作為向字串自身傳送的do:訊息的實際參數,從而將這個字串所包含的每個字元,都作為實際參數傳送給這個迭代塊,而各做一次求值。在求值迭代塊的時候,通過value:訊息,將迭代元素傳送給aBlock所繫結的選擇塊,它回答一個布林值;接著向它傳送ifTrue:訊息,如果這個布林值是對象true,則將這個字元增加到要返回的字串中。

字串類String回應do:訊息,要呼叫的do:方法,定義在可迭代類Iterable[19],而實現在可序列化搜集類SequenceableCollection[20],這個類是Iterable類的子類和String類的超類。

例外處理

Smalltalk的例外處理機制,Exception類及其子類比如Error類,類似於CLOS的例外處理樣式,使用塊作為處理器:

[ 一些运算. 
  Error signal: 'an error occurred'.
  另一些运算
] on: Error do: [ :ex | 
  处理器代码. 
  ex return ]

例外處理器的ex實際參數,提供對掛起運算的狀態的訪問,比如它的堆疊框、行號、接收者和實際參數等,並且通過傳送ex proceedex rejectex restartex return之一,還可用來控制計算怎樣繼續。

類通過實例變數定義它的實例的結構,通過方法定義它的實例的行為。每個方法都有叫做選擇子的一個名字,它在這個類之內是唯一性的。

定義

下面是個平凡的類別定義[21]

Object subclass: #MessagePublisher
  instanceVariableNames: ''
  classVariableNames: ''
  poolDictionaries: ''
  category: 'Smalltalk Examples'

多數這種定義經常由編程環境來填充。這裡的類別定義是給Object類的一個訊息,用來建立它叫做MessagePublisher的一個子類。

在Smalltalk中類是頭等對象,它可以就像任何其他對象一樣接收訊息,並可以在執行時間動態的建立。Object類在收到這個subclass:instanceVariableNames:classVariableNames:poolDictionaries:category:訊息之時,原則上首先在其元類Object class中尋找對應方法,未果而上溯繼承鏈在其超類Class類中找到對應方法實現。

在Smalltalk中,實例變數英語Instance variable是這個實例的私有變數,它可以在定義它們的類的任何實例方法中,還有在它的子類中定義的方法中,通過名字來訪問。在需要於一個類的所有實例、這個類本身和它的子類之間,共享某個資料的時候,要使用類別變數英語Class variable,它是由這個類和它的所有實例共享的私有變數。池變數是在可以沒有繼承關聯的多個類之間共享的變數。池變數最初儲存在池字典中,現在它們應當被定義為專門的類(SharedPool的子類)的類別變數。

category指示有關的類的「群組」,在現代Smalltalk版本如Pharo中被替代為package,包系統是利用簡單的命名約定,組織SqueakPharo原始碼的簡單而輕量級的方式。

在一個類所對應的元類中定義的實例變數叫做類別實例變數,每個類都有自己私有的類別實例變數,子類將繼承這些類別實例變數,但是子類會擁有這些變數的它們自己的私有複本,類和它們的子類不分享類別實例變數。例如,可以定義一個類別實例變數叫做count來跟蹤一個給定的類有多少實例。

類不能直接訪問它的實例的實例變數,而實例不能訪問它們的類的類別實例變數。如果需要的話必須定義變異子與訪問子

方法

當一個對象接收到一個訊息的時候,呼叫匹配這個訊息名字的一個方法。所有方法都是公開的和虛擬的(也就是動態尋找的)。方法被分組入指示它們意圖的協定(protocol)之中。

為一個類增加方法涉及到Behavior類,其中的compile:方法,編譯一個方法原始碼並返回一個CompiledMethod類別的實例;addSelector:withMethod:方法,將給定的一個編譯過的方法增加到方法字典中。在Behavior類的子類ClassDescription類中,compile:classified:方法,編譯一個方法的原始碼,並為這個方法指派上給定的歸類(對於方法稱為協定)。

對象負責在執行時間動態的確定,執行哪個方法來回應一個訊息,儘管在很多語言中,這可能是(有時或總是)在編譯時間靜態確定的。下列代碼定義一個方法publish,並且這個定義將在這個對象收到publish訊息的時候發生。

publish
  Transcript show: 'Hello World!'

下列名字為#quadMultiply:and:的方法,演示了接收多個實際參數並返回一個值:

quadMultiply: i1 and: i2
  "这个方法将给定的两个数相乘并对结果乘以4."
  | mul |
  mul := i1 * i2.
  ^mul * 4

執行任何前導了^脫字元)的表達式,都導致這個方法於這一點退出,並返回這個表達式的值。終止而沒有顯式返回某個表達式的一個方法,將隱含的返回自身。

實例化

為一個類新建一個實例,要用到new方法,它定義在Behavior類中。下列例子代碼:

MessagePublisher new

建立並返回MessagePublisher類的一個新實例。它典型的會被賦值到一個變數:

publisher := MessagePublisher new

但是也可以向一個臨時的匿名對象傳送一個訊息:

MessagePublisher new publish

類別方法

類別方法就是在一個類的元類中定義的方法。

比如搜集類Collection的類別方法中,實例建立方法有with:with:with:[22]、一直到with:with:with:with:with:with:方法。在下面的例子中,將with:with:用於有序搜集類OrderedCollection,它是SequenceableCollection類的子類,故而能最終上溯至超類搜集類:

| rectangles aPoint collisions |
rectangles := OrderedCollection
  with: (Rectangle left: 0 right: 10 top: 100 bottom: 200)
  with: (Rectangle left: 10 right: 10 top: 110 bottom: 210).
aPoint := Point x: 20 y: 20.
collisions := rectangles select: [:aRect | aRect containsPoint: aPoint].

反射

反射是一個電腦科學術語,適用於有能力檢查它們自己的結構的軟體程式,例如檢查它們的分析樹或輸入和輸出參數的資料類型。反射是動態、互動式語言比如Smalltalk和Lisp的一個特徵。具有反射的互動式程式(要麼解釋的要麼編譯的)維護所有主記憶體內對象的狀態,包括代碼對象自身,這是在解析/編譯期間生成的,並且是在編程上可訪問和修改的。

反射也是Smalltalk這種有元模型的語言的一個特徵。元模型是描述這個語言的模型,開發者可以使用元模型來做事,比如遊歷、檢查和修改一個對象的分析樹,或找到特定種類的結構的所有實例(例如在元模型中Method類的所有實例)。

Smalltalk-80是完全的反射式系統,用Smalltalk-80語言實現。Smalltalk-80提供了結構性和計算性反射二者。Smalltalk是結構性反射式系統,其結構是由Smalltalk-80對象定義的。定義這個系統的類和方法也是對象,並且完全是它們所有助力定義的系統的一部份。Smalltalk編譯器將文字原始碼編譯成方法對象,典型是的CompiledMethod的實例。通過把它們儲存入一個類的方法字典,而增加到這個類。類層級的定義類的那部份,可以向系統增加新類。這個系統是通過執行建立或定義類和方法的Smalltalk-80代碼來擴充的。Smalltalk-80系統是個現場(living)系統,承載著在執行時間擴充自身的能力。

因為類是對象,可以向它們提問比如:「你實現了哪些方法?」或「你定義了什麼欄位/槽/實例變數?」。所以通過能應用於系統中的任何對象的普通的代碼,對象可以輕易的檢查、複製、(去)序列化,諸如此類[23]

Smalltalk-80還提供計算性反射,有能力觀察系統的計算狀態。在衍生自最初Smalltalk-80的語言中,一個方法的當前活動(activation),可以作為通過偽變數命名的一個對象來訪問,這個偽變數是作為六個保留字之一的thisContext。通過傳送訊息至thisContext,一個方法活動可以提問比如:「誰給你傳送了這個訊息?」。這些設施使得有可能實現協程,或類似Prolog回溯,而不需要修改虛擬機器。異常系統也是使用這個設施實現的。這個設施更有趣的用法之一,是在Seaside英語Seaside (software) web框架之中,它通過為每個編輯的頁面儲存續體,並在它們之間切換來導航一個web站點,緩解了編程者處理Web瀏覽器的返回按鈕的複雜性。使用Seaside編程web伺服器,可以使用更常規的編程風格來完成[24]

Smalltalk如何使用反射的一個例子,是處理錯誤的機制。當一個對象被傳送了一個它沒有實現的訊息的時候,虛擬機器傳送給這個對象doesNotUnderstand:訊息,具有這個訊息的實化作為實際參數。這個訊息(它是另一個對象,是Message的實例),包含這個訊息的選擇子和它的實際參數的一個Array。在互動式Smalltalk系統中,doesNotUnderstand:的預設實現,是打開一個錯誤窗口(一個Notifier)向使用者報告錯誤。通過它和反射設施,使用者可以檢查錯誤在其中發生的上下文,重新定義犯錯的代碼,並繼續,這一切都在這個系統之中,使用Smalltalk-80的反射設施[25][26]

通過建立只理解(實現)doesNotUnderstand:的一個類,可以建立一個實例,經由它的doesNotUnderstand:方法能攔截傳送給它的任何訊息。這種實例可以叫做透明代理(proxy)[27]。可以使用這種代理來實現很多設施,比如分散式Smalltalk,這裡的訊息在多個Smalltalk系統之間交換,和資料庫介面,這裡的對象透明的從資料庫中排除錯誤,還有promise等。分散式Smalltalk的設計影響了如CORBA這樣的系統。

基於映像的持久儲存

多數流行的編程系統,將靜態的程式碼(以類別定義、函式或過程的形式),分離於動態的或執行時間的程式狀態(比如對象或其他形式的程式資料)。它們在程式啟動的時候裝載程式碼,而任何先前的程式狀態必須從設定檔或其他資料來源顯式的重新建立。程式(和編程者)未顯式儲存的設定,在每次重新啟動時都必須再次設立。傳統的程式在每次程式儲存一個檔案、退出和多載的時候,還失去很多有用的文件資訊。這會失去細節比如回退歷史或游標位置。基於映像的系統不會因為電腦關閉或OS更新,而強制失去所有這些東西。

但是很多Smalltalk系統,不區分程式資料(對象)和代碼(類)。事實上,類也是對象。因此,多數Smalltalk系統,儲存整個程式狀態(包括類和非類對象二者)在一個映像英語system image檔案之中。這個映像可以接著由Smalltalk虛擬機器裝載,將類Smalltalk系統恢復成先前的狀態[28]。這是受到了FLEX的啟發,它是Alan Kay建立的語言並描述於他的科學碩士畢業論文中[29]

Smalltalk映像類似於(可重新啟動的)核心轉儲,並可以提供與核心轉儲相同的功能,比如延遲或遠端除錯,具有對出錯時刻的程式狀態的完全訪問。將應用代碼建模為某種形式的資料的其他語言比如Lisp,也經常使用基於映像的持久儲存。這種持久儲存的方法,對於快速開發是強力的,因為所有開發資訊(比如程式的解析樹),都儲存而利用於除錯。但是它作為一個真實的持久儲存機制,也有一個嚴重的缺點。首先,開發者可能經常想要隱藏實現細節,並使它們在執行時間不可獲得。出於法律和維護的原因,允許任何人在執行時間修改程式,對於在執行時間環境不暴露原始碼的編譯後的系統,不可避免的介入複雜性和潛在的錯誤。其次,儘管持久儲存機制易於使用,它缺乏多數多使用者系統需要的真正持久儲存能力。最明顯的是進行同多個使用者並列訪問相同的資料庫的事務[30]

實現列表

OpenSmaltalk

OpenSmaltalk VM(OS VM)是Smalltalk執行時環境的著名實現,很多現代Smalltalk VM基於或衍生自它[31]。OS VM自身是從一組Smalltalk原始碼檔案(它們叫做VMMaker),轉譯成原生C語言原始碼(通過使用叫做Slang的轉譯器[32][33]),它依次再針對特定平台和硬體架構來編譯,實際上確使Smalltalk映像的跨平台執行。原始碼可以在GitHub上獲得並在MIT許可證下發布。OS VM的知名衍生者有:

  • Squeak,一個開源Smalltalk。
  • Pharo Smalltalk,一個開源跨平台語言。
  • Croquet VM,Croquet OS的一個與Squeak有關的Smalltalk VM。
  • Cuis-Smalltalkf[34],一個開源的小型、簡潔和適用的Smalltalk。
  • Haver-Smalltalk[35],Cuis的具有完整模組系統的擴充。

JavaScript VM

其他

參見

參照

延伸閱讀

外部連結

Loading related searches...

Wikiwand - on

Seamless Wikipedia browsing. On steroids.

Remove ads