開篇之前先說明一下,我和老莊有著不錯的私交,他最初寫喪鐘系列的時候,我是忠實的擁躉之一,莊兄見我尚有寸尺所長,還在喪鐘系列里引用了我的幾個觀點。然而最近一段時間里,我作了這樣幾件事情:

1.重新學(xué)習(xí)了lambda演算。我的數(shù)學(xué)基礎(chǔ)不及taowen等,費了一些時日仍不完全了然,目前大抵能對著R5RS后面的語義定義對一些簡單的scheme程序進(jìn)行l(wèi)ambda演算。

2.反復(fù)研究了幾遍SICP。同時看了錄像和書(感謝曹老師下的Video),自信對前二章半有些許認(rèn)識書中未及,后二章半粗知大覽尚不能發(fā)前人所未發(fā)之言。

3.學(xué)習(xí)了Smalltalk和Ruby,Duck Typing以及其他一些有關(guān)類型系統(tǒng)的東西。

4.回顧了一下面向?qū)ο笳Z言的一些發(fā)展史,以史為鑒粗知一些興替。

5.經(jīng)日和taowen,老莊討論語言、設(shè)計等等之類,每為一辨必窮我所知爭發(fā)一言,所以知識可以迅速雜糅:D
經(jīng)過種種之后,發(fā)現(xiàn)當(dāng)初所謂鳴OO之喪鐘,實在是言過其實。
--------------------------------------------------------------------------------------------------------------------------------------------------------------
第0 關(guān)于"面向?qū)ο?
既然要為人家敲喪鐘,先要找對了苦主,不然豈不是白忙活了一場。什么是面向?qū)ο螅窟@個問題太大,我們舉一個很常見的例子:
這個例子自然很不充分,不過繼承啊,接口啊,多態(tài)啊,略舉大概,尚可以作為一個討論的例子。從這個例子里,我們發(fā)現(xiàn)有這樣一個事實,就是Person類有兩個方法

void playSolo(Instrument instrument);

void playBackground(Instrument instrument);
分別表示,需要一個類型為Instrument的對象instrument,然后對他進(jìn)行相應(yīng)的操作,而且,從概念上來講,第一個方法,使演奏旋律的,也就是單音,而第二個方法是演奏和聲的。那么,如果我有一個類
我們知道,玻璃瓶裝了水也是可以發(fā)出聲音,Mozart可是為玻璃瓶寫過曲子的,而這里我們卻不能

GlassBottle beerBottle =

.

vincent.playSolo(beerBottle);
因為我們在構(gòu)造Person類的時候,我們的play方法是依賴一個類型為Instrument的Object,換而言之,我們這里所謂的
Object-Oriented其實稱作Object-with-Type-Oriented的更合適,其實這類語言有一個專有名詞,叫做static
type object oriented。那Object-Oriented是不是就一定是Static Type Object
Oriented的呢?不然,比如在Ruby里,我們可以寫成:
然后就可以

perez711 = Guitar.new

vincent = Person.new

vincent.playSolo(perez711)
同樣也可以

vincent.playSolo(GlassBottle.new)
在這里,類型可以推演也可以留到runtime檢查,如果是推演的話,在playNote里,由于thing調(diào)用了playNote方法,所以傳進(jìn)
來的對象必須要刻意接受playNote這個消息,于是Guitar和GlassBottle都是可以通過,因此我即可以演奏我的Perez
711,也可以敲玻璃瓶。這種方式叫做dynamic type。
那么static type和dynamic type區(qū)別在那呢?static
type認(rèn)為,class就是type,type就是class;
subclass就是subtyping,subtyping就是subclass(其實這句不嚴(yán)謹(jǐn),在c++里可以使得subclass不是
subtyping,但是java里就沒辦法了);而dynamic type則認(rèn)為類型和class沒有關(guān)系,類型取決于一個對象能夠接受的消息。
那么哪個才是面向?qū)ο笾婷材兀窟z憾的說,static type并不是最初的面向?qū)ο蟆R詫ο笞鳛槟K化的單位,始自Simula
67。但是Simula
67并不是一個面向?qū)ο笙到y(tǒng),以目前的觀點來看,充其量是Object-based,第一個面向?qū)ο笳Z言當(dāng)推Smalltalk,Smalltalk是
dynamic type的。不過Smalltalk不太流行,第一個大面積流行的面向?qū)ο笳Z言是C++,C++是static
type的,正如Lisp是第一個函數(shù)式編程語言,很多Lisp的特性被當(dāng)作函數(shù)式語言的共有特性(比如表是最重要的數(shù)據(jù)結(jié)構(gòu),輕語法)一樣,所以很多人
以為面向?qū)ο缶捅厝皇莝tatic type(比如老莊和我)。對于面向?qū)ο蟮恼`解,80%來自C++。也就是說,80%來自于static
type的面向?qū)ο笙到y(tǒng)。將static type面向?qū)ο髿w咎于整個面向?qū)ο螅逸厡嵲谑谴蠖鵁o當(dāng)言過其實。
后面我再一一將老莊所言之OO不當(dāng)之處加以說明
第1 關(guān)于"接口"
關(guān)于接口的問題老莊說對了,這個東西并不屬于面向?qū)ο蟮母拍睿以趧討B(tài)類型面向?qū)ο笳Z言(比如Ruby, Smalltalk里),根本沒有這個東西。這是一個純粹的靜態(tài)類型面向?qū)ο笳Z言的特性,或者直接說,接口就是一個純類型(Type)。還是上次的例子:
1


interface Instrument

{
2

void playNote(Note note);
3

void playChord(Chord chord);
4

}
5

6

in Person Class
7

8


void playSolo(Instrument instrument)

{
9

10

}
在我接觸Smalltalk的最開始的一段時間里,這種地方是讓我最難受,我已經(jīng)習(xí)慣了用類型輔助我的思維,但是我發(fā)現(xiàn)我在Smalltalk里做不到,雖然我可以寫出

Instrument>>playNote:note

^self subclassResponsibility.


Instrument>>playChord:chord

^self subclassResponsibility.
但是他卻不是一個接口,我可以很用

instrument = Instrument new.

instrument playNote: Note C.
來構(gòu)造一個Instrument之后在它之上調(diào)用playNote方法,然而我會得到一個messageNotUnderstand的錯誤,
Smalltalk和Ruby里沒有Abstract的概念。也就是說abstract method,abstract
class以及interface,都不是面向?qū)ο蟮母拍睿ɑ蛘邍?yán)格一些說,都不是面向?qū)ο蟮谋仨毜母拍睿敲嫦驅(qū)ο箢愋拖到y(tǒng)的概念。那么在
Smalltalk里我們會怎么做呢?

Person>>playSoloOnInstrument:instrument

instrument playNote: Note C;

playNote: Note D;

playNote: Note E.


Person>>playBackgroundOnInstrument:instrument

instrument playChord: Chord C1;

playChord: Chord C1;

playChord: Chord C1;
對于playSoloOnInstrument:instrument,我們對于instrument的類型是有要求的,就是它必須能夠接受playNote這個消息。當(dāng)然這個類型需要是隱性,我也可以對等的寫出靜態(tài)面向?qū)ο蟮拇a把這個隱性的類型顯示的表示出來:
1


interface Instrument

{
2

Instrument playNote(Note note);
3

}
同樣對于第二個方法我們也可以寫出來:
1


interface Instrument

{
2

Instrument playChord(Note note);
3

}
如果我們需要多于一個的消息也是一樣的,比如

Person>>playBWV996OnInstrument:instrument

instrument playNote: Note C;

playChord: Chord C;

playNote: Note D.
同樣這個時候隱性的類型需要就是
1


interface Instrument

{
2

Instrument playNote(Note note);
3

Instrument playChord(Note note);
4

}
那么接口是什么呢?我給出一個不確切的說法, interface是一個消息的發(fā)送者(sender)和一個消息的接受者(reciver)間的一種類型的約定 ,
也就是說在我看來interface的用處主要在細(xì)粒度的顯式類型約定。我有一個同事,每次寫代碼都為一個Test
Case所要測試的對象定義一個interface,每個interface都只有2-3個方法(lx同學(xué)夸你呢:D),這是很得interface之三
味的用法。這種的做法對于在靜態(tài)的面向?qū)ο笙到y(tǒng)的好處我們在繼承里再論述。
至于老莊所說的接口是多繼承的一種代替品,這只不過是世俗的看法,在靜態(tài)類型的面向?qū)ο罄铮^承至少有2個語義:
1.實現(xiàn)繼承,這個在Smalltalk,Ruby,C++里有,而在java里沒有,C++里是通過private extends來實現(xiàn)的
1


class SubClassA: private ImplementationParent

{
2

}
這也是C++里為數(shù)不多的subclass不是subtype的例子。
2.類型繼承,這個在C++和java里有,而在smalltalk,ruby有卻不明顯。
類型繼承的極致就是C++里的純虛父類
1


abstract class Parent

{
2

3

public asbtract void method1() = 0;
4

public asbtract void method2() = 0;
5

}
也就是java里的interface
1


interface Parent

{
2

void method1();
3

void method2();
4

}
因此,也就明了了,所謂“面向接口編程”是以類型作為約定的編程。我覺得這點大家一定不陌生,面向接口的編程里interface都很小而且約定明
確。但是要說明一點的是,這個東西和"面向抽象而不要面向具體編程"其實還不一樣,所以這個東西也就僅僅能算是靜態(tài)類型面向?qū)ο蟮囊粋€慣用法,還到不了原
則這么高。
第2 繼承
前面已經(jīng)說過了,繼承至少有2個語義: 實現(xiàn)繼承和類型繼承,在說明這兩個東西之前,我們繼續(xù)來看上面的例子

Person>>playSoloOnInstrument:instrument

instrument playNote: Note C;

playNote: Note D;

playNote: Note E.


Person>>playBackgroundOnInstrument:instrument

instrument playChord: Chord C1;

playChord: Chord C1;

playChord: Chord C1;


Person>>playBWV996OnInstrument:instrument

instrument playNote: Note C;

playChord: Chord C;

playNote: Note D.
現(xiàn)在我們看playBWV996這個消息,BWV996是Bach所寫一個魯特琴組曲,魯特琴是彈撥樂器,同時也是和聲樂器(所謂和聲樂器就是可以演奏和聲)。在很多樂器上都有改編的版本,比如魯特琴的近親吉他等等,這個時候,我們可以實現(xiàn)這樣幾個類
然后我們可以嘗試以此調(diào)用

vincent.playBWV996OnInstrument: Guitar new.

vincent.playBWV996OnInstrument: Lute new.

vincent.playBWV996OnInstrument: Bass new.
最后一個會得到一個messageNotUnderstand的錯誤。也就是說,對于Bass而言由于不能演奏和聲從而不能演奏BMV996(不過這個世界上能人太多了...哎),我們換到靜態(tài)類型面向?qū)ο笙到y(tǒng)來看。
對于第一個方法,playSolo的時候我們要求的類型是能夠演奏單音的。我們可以寫出來
1


interface SoloInstrument

{
2

SoloInstrument playNote(Note note);
3

}
對于第二個方法,playChord的時候我們要求的類型是能夠演奏和弦的,我們可以寫出來
1


interface ChordInstrument

{
2

ChordInstrument playChord(Chord note);
3

}
而對于第三個方法,playBWV996的時候我們要求既能演奏和弦也能演奏單音,這個時候出現(xiàn)一個問題,我們怎么處理Instrument的繼承關(guān)系?一個能演奏和弦的樂器是否可以演奏單音(答案是一般而言是的,但是也不排除有一些不是這樣的)?還是我們簡單的寫:
1


interface SoloAndChordInstrument extends SoloInstrument, ChordInstrument

{
2

}
或者
1


interface BWV996Playable

{
2

3

BWV996Playable playNote(Note note);
4

BWV996Playable playChord(Chord note);
5

}
對于動態(tài)類型簡單的隱性類型約定,顯示的類型系統(tǒng)帶來的一個副作用就是我們必須處理類型之間的關(guān)系。 注意這里是類型之間的關(guān)系,而不是對象之間的關(guān)系 。老莊同志批了很多篇的面向?qū)ο蟮某橄螅嫦驅(qū)ο蟮念愋拖到y(tǒng)以及面向?qū)ο蟮谋倔w論, 其實都在是在類型關(guān)系上折騰,而不是在對象關(guān)系上折騰 。而事實上面向?qū)ο蟮?strong> 類型系統(tǒng)并非必然就是靜態(tài)類型系統(tǒng),而我們的類之間的關(guān)系不一定就和類型的關(guān)系相一致。 就像上例所示,在Smalltalk里,Lute,Guitar和Bass之間沒有任何的繼承關(guān)系,但是對于person的3個消息而言,它們卻是有類型的。
因此老莊所批的,是對象類型系統(tǒng)的抽象能力,而非面向?qū)ο蟮某橄竽芰ΑU缢陬愋拖到y(tǒng)里所給的例子,那張他認(rèn)為很失敗的面向?qū)ο蟮膱D,其實可以完全不依賴?yán)^承來實現(xiàn),而對這個類型系統(tǒng)的消費者而言,他們能夠以一定的類型的觀點,來處理這個系統(tǒng)的對象。
而老莊最后一個結(jié)論:

我的結(jié)論是:“一個類型,是由其本質(zhì)決定了所能表現(xiàn)出的可操作性,而不是有其所能接受的操作決定了其本質(zhì)。然而,OO正好把這個問題搞反了!”
我的看法是,這句話根本就是詭辯,前面半句的主語是“一個類型”,后面半句的主語是"OO"...
雖然前半句是對的,但是換一樣說法可能更好:" 所能接受的操作反映了其本質(zhì) ",
面向?qū)ο蟊旧砭蜎]有說我要做一個本質(zhì)抽象,這一點在Smalltalk的類型判斷操作上的可能是一個佐證,Smalltalk用isKindOf來判斷繼
承關(guān)系,我們來玩一個文字游戲,改成俚語就是kinda,也就是"有一點,有幾分"的意思,而不是說,“就是”,或者“從分類學(xué)上可證明之類的含義”。我
再舉一個齷齪的例子。

vincent ballon: AirBallon new.

vincent ballon: Condom new.
氣球和保險套,對于ballon這個方法而言是一個類型,都是"有幾分"可以吹起來。但是我怎么定義一個精確的本質(zhì)?Ballonable?還是MakeFromLatexAndVeryThin?或者簡單說FlexableAndThin?
在繼承這一點上,我想老莊引文中:Elminster的話是從事物的特征與屬性歸納出它的“類型”。恰恰對于靜態(tài)類型面向?qū)ο笙到y(tǒng)是可行的。如我前
文所述,我把一個object和所有sender的約定(也就是interface),繼承在一起,恰恰就是一個頗為恰當(dāng)?shù)念愋投x。
而對于動態(tài)類型系統(tǒng)里的面向?qū)ο笳Z言,繼承的也有類型繼承的含義,但是并不是唯一的途徑。用一句我們常說的話,在靜態(tài)類型系統(tǒng)里,類型和類是緊耦合的,動態(tài)類型系統(tǒng)中他們的耦合比較松。
從此而觀,所有對于面向?qū)ο蟮恼軐W(xué)考慮以及本體的思考,對于動態(tài)面向?qū)ο笙到y(tǒng)已經(jīng)不是那么迫切了。而把對象類型系統(tǒng)的不足歸咎于面向?qū)ο蟮牟蛔悖菜坪跽摀?jù)不足。
第3. 一切皆對象和面向?qū)ο蟮睦碚摶A(chǔ)
老莊是反對一切皆對象的,而TrustNo1在javaeye的一篇帖子上說:

第一,我可以很負(fù)責(zé)的說,OO的,70年代成型,80年代在理論基礎(chǔ)上就給人斃掉。從這種意義上說不是OO死不死的問題,而是OO還活著么?當(dāng)然理論基礎(chǔ)給人斃掉,不是說沒有用。
我先說面向?qū)ο蟮睦碚摶A(chǔ)的問題,至于一切皆對象稍后再表。
所謂面向?qū)ο蟮睦碚摶A(chǔ)其實是沒有的,原因很簡單,面向?qū)ο蟾揪筒皇?
一種計算模型。在第一次軟件危機(jī)的那個時代,對與計算機(jī)的非數(shù)值計算應(yīng)用的討論以及對于可計算性問題的研究和發(fā)展,大抵確立了幾種主流的計算模型:遞歸函
數(shù)類,圖靈機(jī),Lambda演算,Horn子句,Post系統(tǒng)等等。
其中遞歸函數(shù)類是可計算性問題的數(shù)學(xué)解釋;圖靈機(jī)是圖靈解決可計算問
題的時候所設(shè)計的裝置,其后成為計算機(jī)的裝置模型,與圖靈機(jī)相關(guān)的形式語言和自動機(jī)成為了命令式語言的理論基礎(chǔ);lambda演算成為了函數(shù)式語言的理論
基礎(chǔ);Horn子句是prolog這類邏輯語言的理論基礎(chǔ)。但是我們驚訝的發(fā)現(xiàn),面向?qū)ο鬀]有計算模型的理論基礎(chǔ),換而言之,面向?qū)ο蟾揪筒皇菑目捎嬎?
性的研究上發(fā)展過來的,那么面向?qū)ο蟮睦碚摶A(chǔ)的價值本身就不大。
所以我很奇怪的一個問題就是TrustNo1所謂的面向?qū)ο笤?0年代理論基礎(chǔ)上給人斃掉的說法是從何而來的?既然面向?qū)ο蟊举|(zhì)上不是一種計算模
型,那么它大抵上只能歸結(jié)為一種應(yīng)用技術(shù),應(yīng)用技術(shù)自然可以從各種不同的領(lǐng)域里得到相似的應(yīng)用,那么斃掉的理論基礎(chǔ)所指的又是什么呢?甚怪之。
既然面向?qū)ο蟛皇且粋€計算模型,那么我們可以從不同的角度推斷出OO的各種形態(tài),老莊已經(jīng)出給了從ADT引出OO的問題以及例子,我就不羅嗦了,我
給一個從Functional Programming出來的例子,其實就是SICP里的Data as Procedure。

(define (make-user name age sex)

(define (dispatch message)

(cond ((eq? message 'getName) name)

((eq? message 'getAge) age)

((eq? message 'getSex) sex))

(else (error 'messageNotUnderstand))))

dispatch)
然后我們就可以

(define vincent (make-user 'Vincent 24 'Male))

(vincent 'getName)
自然的,如果我調(diào)用

(vincent 'sayHi)
會得到一個messageNotUnderstand的runtime錯誤,這就是一個很自然dyanmic
type的對象封裝,最早的面向?qū)ο笙到y(tǒng)Smalltalk和CLOS基本上都是這個路子,于是有一個問題,為什么最早的面向?qū)ο笙到y(tǒng)都是dyanmic
type?這里就跟lambda演算有關(guān)了。
lambda演算這個計算模型根本的一個假設(shè)就是,對于任何一個定義良好的數(shù)學(xué)函數(shù),我都可以使用lambda抽象來表述他的求值,因此無論是什么
東西你能夠構(gòu)造lambda抽象的話,我就能計算。這個地方東西很多,大家可以找找lambda演算相關(guān)的資料,這里我說三件事(由于lambda太難輸
入,我用scheme程序代替,然后由于alpha變化,beta規(guī)約和eta規(guī)約我也用scheme偽碼來模擬。)
第一個是數(shù)值的表述,其實這個很簡單,不考慮丘奇代數(shù)的系統(tǒng)的話,我們可以把數(shù)值表示成單值函數(shù):

(define one (lambda (x) 1))
這個東西無論給什么x都返回1,然后根據(jù)lambda演算里的alpha變換,這個lambda抽象等價于數(shù)值1。因此,對于所有的數(shù)值,我們可以按lambda演算處理。
第二個是bool的表達(dá),也就是如何邏輯進(jìn)行l(wèi)ambda抽象,下面我直接給出了,缺省認(rèn)為大家都看了SICP,對Scheme也頗有心得。

(define true-new (lambda (x y) x)) ;;;這個函數(shù)也叫select-first

(define false-new (lambda (x y) x));;;這個函數(shù)也叫select-second


(define if-new (lambda (conditon if-true if-false) (condition if-true if-false)))
然后我就可以做一個測試

(if-new true-new 3 4)

3


(if-new false-new 3 4)

4
因此,對于所有bool我們可以按lambda演算來處理
第三個是自定義類型,這里我們還是先看一個Lisp里的例子,序?qū)Α?/p>

(define (cons a b) (lambda (dispath) (dispatch a b)))

(define (car list) (list select-first))

(define (cdr list) (list select-second))
這里依舊是high-order,我們來測試

(define list1 (cons 1 2))

(car list1)

1

(cdr list1)

2
這里大家自己用beta規(guī)約算一下,就發(fā)現(xiàn)的確是這樣的。這里我們又可以看到,在lambda演算里,根本沒有數(shù)據(jù)或者類型。有的永遠(yuǎn)各種各樣的
lambda抽象而已(目前已經(jīng)有了帶類型的lambda演算)。這里說一句題外話,SICP里的Data as
Procedure其實就是在說這個問題,不過他沒明說,而是用了一種特殊的用法而引出了消息傳遞風(fēng)格,我覺得這里SICP有些顧此失彼,對于data
as procedure我總結(jié)的一般形式是

(define (construct-function value1 value2 value3 value4

valuen) (lambda (dispatch) (dispatch value1 value2 value3 value4

valuen)))

(define (select-function1 data) (data select-first))

(define (select-function2 data) (data select-second))


.

(define (select-functionn data) (data select-last))
綜上所述,我們看到在lambda演算里,一切都是lambda抽象,然后對于不同的lambda抽象使用alpha變換,beta規(guī)約和eta規(guī)約,表述各種不同計算。看,在面向?qū)ο笾熬鸵呀?jīng)有了一切皆某某的完美的計算理論存在了。而且回顧一下:

(define (make-user name age sex)

(define (dispatch message)

(cond ((eq? message 'getName) name)

((eq? message 'getAge) age)

((eq? message 'getSex) sex))

(else (error 'messageNotUnderstand))))

dispatch)


(define vincent (make-user 'Vincent 24 'Male))

(vincent 'getName)
我們有理由說,對象其實就是一個lambda抽象,所以一切皆對象不過是一切皆lambda抽象的演化,這也是為什么SICP里把面向?qū)ο蠓Q作一種“方便的界面”而不是一種抽象的方法的原因了。那么對象和lambda抽象又什么區(qū)別呢?
嘿
嘿,熟悉FP的人都應(yīng)該知道了,就是Side-Effect,副作用。對象允許對其內(nèi)部狀態(tài)進(jìn)行修改,那么這個東西就破化了eta規(guī)約的前提條件,也就是
說允許修改內(nèi)部狀態(tài)的東西,已經(jīng)不是一個可以進(jìn)行l(wèi)ambda計算的lambda抽象了。因此暫且給一個別的名字吧,就叫對象吧.....因此我認(rèn)為,對
象很大程度上是一種帶有副作用的lambda抽象。
我在有一篇blog上寫了Say sorry to
object-oriented,里面給了一只用對象作分支的例子,這里就不重復(fù)了,有興趣大家可以去看一下(剛才好像在JavaEye上看到一個說法,
說Everything is Object是Smalltalk的廣告語,唉,Smalltalk里的的的確確everything is
object啊。)
這里我們來總結(jié)一下,面向?qū)ο笞鳛橐环N“方便的界面”主要解決了一個局部化和模塊化的問題,這是從lambda演算和
函數(shù)編程的角度來看面向?qū)ο蠹夹g(shù)。(taowen在他的一篇blog上說,面向?qū)ο缶植炕薙ide-Effect,我深以為然),這個東西我個人認(rèn)為更
加接近面向?qū)ο蟊緛淼囊馑迹皇怯葾DT里發(fā)展出來的帶類型的對象系統(tǒng)的那個意思。因此老莊不以為然的面向?qū)ο箢愋拖到y(tǒng),我也不以為然。但是面向?qū)ο笞?
為lambda抽象的界面,我覺得還是很不錯的。這一點在Smalltalk和Ruby里都有不錯的體現(xiàn)。