值得懷念的世界,卻不值得回去!
——莊表偉
面向過(guò)程的世界是完整的,統(tǒng)一的,也是容易理解的——對(duì)于程序員來(lái)說(shuō)——或者說(shuō)他只需要一種理解能力。這個(gè)世界雖然值得懷念,卻不值得再回去。因?yàn)椋覀儾辉傧癞?dāng)年的程序員那樣,只開(kāi)發(fā)那些簡(jiǎn)單的軟件了。很多人崇拜那些早起的“大牛”,其實(shí)平心而論,我們現(xiàn)在面對(duì)的問(wèn)題的復(fù)雜程度,在他們當(dāng)年可以說(shuō)幾乎無(wú)法解決。需求的復(fù)雜程度也不是他們當(dāng)年能夠設(shè)想到的。
這是在秘魯發(fā)現(xiàn)的神秘的納斯卡巨畫,這樣巨大的地面藝術(shù),可以給我們對(duì)于面向過(guò)程的編程的結(jié)論一個(gè)可視化的比喻。面向過(guò)程的編程,只有一個(gè)統(tǒng)一的世界,他對(duì)于軟件的理解,始終不會(huì)離開(kāi)計(jì)算機(jī)的操作與運(yùn)算本質(zhì),這就像在平地上作畫那樣,我們需要的一根長(zhǎng)1米的直線,非常容易,兩點(diǎn)一線,一拉就出來(lái)了。但是當(dāng)我們需要在地面上畫一根5000米甚至更長(zhǎng)的直線時(shí),如何保證畫出一條直線,就成為一個(gè)巨大的挑戰(zhàn)。當(dāng)視角無(wú)法升到足夠的高度時(shí),如此復(fù)雜的圖案幾乎是無(wú)法把握的。僅僅依靠結(jié)構(gòu)化的劃分,并不能完全的隔離復(fù)雜度的交互影響。單步跟蹤一個(gè)1000行代碼的程序并不困難,但是如果是100萬(wàn)行代碼,甚至更多呢?
再看一張照片:

這是世界上最大的“埃及胡夫金字塔”。我們假設(shè),如果當(dāng)年法老在工程進(jìn)行到80%的時(shí)候,提出需求變更,希望金字塔尖能夠向右移動(dòng)10米。情況會(huì)如何?——會(huì)死好多勞動(dòng)人民的!如果希望向右移動(dòng)100米呢?如果希望有四個(gè)塔尖各在一個(gè)方向呢?如果。。。還好這一切都沒(méi)有發(fā)生,否則我們就不可能看到一個(gè)真正完工的金字塔。然而在軟件開(kāi)發(fā)領(lǐng)域,當(dāng)“結(jié)構(gòu)化編程”面對(duì)“移動(dòng)金字塔”的需求變更時(shí),它只能破產(chǎn)!
可以得出一個(gè)比較關(guān)鍵性的結(jié)論是:
僅僅從計(jì)算機(jī)的角度出發(fā),對(duì)于更為復(fù)雜的需求,描述力不足。對(duì)于巨大的需求變更,應(yīng)變力不足。而這正是對(duì)于的軟件需求的必然發(fā)展趨勢(shì)。
所以,那個(gè)世界不值得回去,但是,OO真的幫到我們了嗎?
多年以后的今天,我們依然在思考這樣一個(gè)問(wèn)題:“OO怎么就流行起來(lái)了呢?”學(xué)術(shù)一點(diǎn)分析,這個(gè)問(wèn)題可以分為以下幾個(gè)部分:
1、OO之前的軟件開(kāi)發(fā)困境何在?
2、當(dāng)時(shí)的開(kāi)發(fā)人員如何解決那些困境?
3、那些解決困境的努力,為何會(huì)匯入OO的名下?
4、OO這個(gè)概念,從何而來(lái)?
5、OO的核心內(nèi)容是什么?
6、OO的實(shí)際目的是什么?
7、OO的理想目標(biāo)是什么?
困境
“需要一個(gè)超越于機(jī)器執(zhí)行層面的的認(rèn)識(shí)。”或者說(shuō),不能僅僅以“解空間”的語(yǔ)言描述解決方案,最好能夠以“問(wèn)題空間”的語(yǔ)言描述解決方案。這是OO得以流行的真正動(dòng)力,因?yàn)镺O宣稱自己能夠更好的描述“真實(shí)世界”。
注意我要區(qū)分的幾個(gè)概念:“解決困境的努力”、“困境的根本原因”、“OO所宣稱的目標(biāo)”、“OO實(shí)際達(dá)到的效果”。因?yàn)樵谝酝腛O的宣傳中,這些概念是一個(gè)有機(jī)的整體,而卻認(rèn)為,其中有諸多“斷裂破碎”之處。
面向過(guò)程的編程,面對(duì)的困境其實(shí)相當(dāng)多,最根本的原因前面也已經(jīng)指出了。但是在當(dāng)時(shí),在具體的項(xiàng)目中,在特定的人看來(lái),他們碰的,是各自不同的問(wèn)題。在人工智能領(lǐng)域,在圖形化界面領(lǐng)域,面對(duì)的是模擬的問(wèn)題。在企業(yè)應(yīng)用領(lǐng)域,面對(duì)的是數(shù)據(jù)訪問(wèn)與保護(hù)的問(wèn)題。從共同的困境來(lái)看,適應(yīng)變更,方便重用,系統(tǒng)健壯之類的要求,也是需要考慮的。
概念的發(fā)展歷程
首先聲明,這是一個(gè)假想的歷程,并非真實(shí)的歷史。真實(shí)的歷史,可以參考以下URL中的介紹:
http://heim.ifi.uio.no/~kristen/FORSKNINGSDOK_MAPPE/F_OO_start.html
模擬:模擬的概念由來(lái)已久,但是如何模擬卻是一個(gè)大問(wèn)題。
抽象數(shù)據(jù)類型(ADT):在對(duì)結(jié)構(gòu)(structure)進(jìn)行簡(jiǎn)單的擴(kuò)展之后,ADT就順理成章的出現(xiàn)了。
封裝:對(duì)于ADT的理論總結(jié)可以表述為“封裝帶來(lái)了數(shù)據(jù)的安全性”。
繼承:一堆不能重用的代碼是“罪惡”的。繼承首先出來(lái)試圖解決這個(gè)問(wèn)題。
多態(tài):是一個(gè)意外的收獲,如果各個(gè)對(duì)象都提供同一個(gè)名字的方法,似乎就可以不去管他們的區(qū)別。
在這些努力與嘗試之后,面向?qū)ο髾M空出世,從哲學(xué)的高度總結(jié)了這些努力:“你們都是正確努力的一部分,你們都在試圖更好的描述真實(shí)世界,真實(shí)的世界里一切都是對(duì)象,所有的對(duì)象在一個(gè)分類系統(tǒng)里各安其位,對(duì)象之間通過(guò)消息相互‘招呼’。應(yīng)用OO思想,描述真實(shí)世界的運(yùn)行,就是編程的主要工作。”
但是事實(shí)上呢?編程并不是描述真實(shí)世界!而是描述需求世界。不同的需求世界,需要不同的“世界觀”。這一點(diǎn),面向?qū)ο蟛](méi)有考慮到。當(dāng)時(shí)流行的思想是通用編程語(yǔ)言,使用一種語(yǔ)言解決世界上所有的開(kāi)發(fā)難題。而要整體解決各不相同的開(kāi)發(fā)難題,只能將目光投向“真實(shí)世界”,那是各個(gè)差異巨大的“問(wèn)題空間”的唯一一致的背景。
面向?qū)ο蟮恼軐W(xué)破綻
在此特別感謝徐昊,這一部分該如何寫,我始終沒(méi)有想好,在與他討論之后,我基本理出了一個(gè)思路。
面向?qū)ο笥袃蓚€(gè)哲學(xué)基礎(chǔ),原子論與形而上學(xué)。這兩大基礎(chǔ),在哲學(xué)的發(fā)展歷程中,曾經(jīng)如日中天,無(wú)可置疑(在古希臘那時(shí)候),如果說(shuō)這樣的哲學(xué)不偉大,那就是我太狂妄了,但是如果有人說(shuō):“西方哲學(xué)在之后的幾千年里沒(méi)有進(jìn)步,古希臘哲學(xué)就是西方哲學(xué)的頂點(diǎn),因此面向?qū)ο罄硭?dāng)然的應(yīng)該建立于這兩大哲學(xué)之上!”你會(huì)相信嗎?
1、原子論
西方哲學(xué)的發(fā)展,經(jīng)歷了兩次變革,一次是認(rèn)識(shí)論轉(zhuǎn)向;一次是語(yǔ)言轉(zhuǎn)向;第一次轉(zhuǎn)向使哲學(xué)的基礎(chǔ)從本體論和形而上學(xué)變?yōu)檎J(rèn)識(shí)論,從研究超驗(yàn)的存在物轉(zhuǎn)到研究認(rèn)識(shí)的主體和主客體關(guān)系;第二次轉(zhuǎn)向把對(duì)主客體的關(guān)系的研究變成了對(duì)主體間的交流和傳達(dá)問(wèn)題的研究。把對(duì)主體的研究從觀念和思想的領(lǐng)域轉(zhuǎn)到了語(yǔ)言的領(lǐng)域(語(yǔ)句及其意義);這兩次轉(zhuǎn)向的代表人物分別是笛卡爾和維特跟斯坦。
————《OO, OO以后, 及其極限》waterbird
看來(lái)我可能是比較淺陋了,在我看了waterbird的《OO, OO以后, 及其極限》之后,曾經(jīng)深深的自責(zé)過(guò)。看來(lái)OO沒(méi)有我想的那么土,不是直接來(lái)自古希臘哲學(xué),而是從維特根斯坦繼承而來(lái)的。waterbird的引用而后總結(jié)的一段維特根斯坦的話,使我對(duì)維特根斯坦大為佩服。
小結(jié)2: 2 主要說(shuō)明 --- 事實(shí)(facts)由原子事實(shí)(atomic facts)所組成;原子事實(shí)(atomic
facts)由更基本的對(duì)象(objects)所組成;我們的關(guān)于外部世界的主觀描述圖畫,與它所描述的外部世界具有相同的邏輯結(jié)構(gòu);注:
(這即是相當(dāng)于軟件開(kāi)發(fā)中的"建模")
還好,在我昨天列出了閱讀書目之后,gigix提醒我看了另外一篇文章:《維特根斯坦早期思想及其轉(zhuǎn)變》,這是一個(gè)正兒八經(jīng)的哲學(xué)家的文章,總算讓我見(jiàn)識(shí)到了軟件開(kāi)發(fā)這個(gè)行當(dāng)里,頗有些不懂哲學(xué)的家伙,拿著哲學(xué)來(lái)唬人的。
原子事實(shí)是最簡(jiǎn)單的事實(shí),無(wú)法再?gòu)闹蟹治龀銎渌聦?shí),分析的結(jié)果只能是對(duì)象。因此,原子事實(shí)是對(duì)象的結(jié)合或配置。“對(duì)象是簡(jiǎn)單的”〔2.02〕,不可再加以分析,所以,對(duì)象就是簡(jiǎn)單對(duì)象,不過(guò),為清楚起見(jiàn),維特根斯坦還是經(jīng)常采用“簡(jiǎn)單對(duì)象”這個(gè)說(shuō)法。簡(jiǎn)單對(duì)象這個(gè)概念引起很多困惑和爭(zhēng)論,其實(shí)維特根斯坦自己也很猶豫,他在筆記中寫道:“我們的困難是,我們總說(shuō)到簡(jiǎn)單對(duì)象,卻舉不出一個(gè)實(shí)例來(lái)。”
他曾考慮過(guò)關(guān)系、性質(zhì)、視域上的小片、物理學(xué)里的物質(zhì)點(diǎn)。他還說(shuō)個(gè)體如蘇格拉底、這本書等,“恰恰起著簡(jiǎn)單對(duì)象的作用”。一條可能的思路是把簡(jiǎn)單對(duì)象理解為一種邏輯要求,一個(gè)邏輯終點(diǎn):“簡(jiǎn)單對(duì)象的存在是一種先天的邏輯的必然性。”
在《邏輯哲學(xué)論》中,維特根斯坦大致采用了這條路線,這本書里從未舉例說(shuō)明什么是簡(jiǎn)單對(duì)象。
維特根斯坦說(shuō)的對(duì)象,是OO中的對(duì)象嗎?一個(gè)正兒八經(jīng)的現(xiàn)代哲學(xué)家的困惑,OO大師們考慮到了嗎?只有樸素、甚至可以說(shuō)是幼稚的原子論觀點(diǎn),才會(huì)輕松的混淆:事實(shí)、原子事實(shí)、對(duì)象和具體的物質(zhì),物體。對(duì)于OO來(lái)說(shuō),對(duì)象非常容易被發(fā)現(xiàn),幾乎隨手一指,都能點(diǎn)到一個(gè)對(duì)象。
從語(yǔ)言哲學(xué)來(lái)說(shuō),最為困難的是:“有沒(méi)有一種語(yǔ)言,可以清晰地、完整地描述這個(gè)世界?”邏輯原子論原本認(rèn)為有可能。但是,維特根斯坦的后期哲學(xué)轉(zhuǎn)向,恰恰指出了一個(gè)困境,而這個(gè)困境即時(shí)是人類歷史上最為天才的頭腦,也無(wú)法“走出”的。德國(guó)人最具有理性的思維能力,而其中最為天才的頭腦卻碰上了理性思維的天花板。維特根斯坦很難理解,越是德國(guó)天才,他的語(yǔ)言越是晦澀。倒是從中國(guó)哲學(xué)的角度,往往能夠看透其中的掙扎。老子在幾千年前就說(shuō):“道可道,非常道,名可名,非常名”。因?yàn)樵噲D準(zhǔn)確、無(wú)誤、無(wú)失漏的命名、描述世界的努力,是不可能成功的。
因此,我現(xiàn)在可以斷言,面向?qū)ο蟊澈蟮脑诱摚贿^(guò)是直接師承古希臘哲學(xué)的簡(jiǎn)單、樸素、幼稚的原子論,這樣的原子論哲學(xué),早已破產(chǎn)。作為哲學(xué)史的研究對(duì)象,自有其價(jià)值,而作為指導(dǎo)軟件開(kāi)發(fā)那么現(xiàn)代活動(dòng)的哲學(xué)理論,實(shí)在是太不適用了。
2、形而上學(xué)
當(dāng)我寫下這個(gè)標(biāo)題的時(shí)候,內(nèi)心無(wú)比惶恐。這么大個(gè)題目,是我這個(gè)半路出家,Google成才的家伙能夠談?wù)摰膯幔慷嗌僬軐W(xué)家一輩子“皓首窮經(jīng)”,也不過(guò)就是研究個(gè)形而上學(xué)啊。
當(dāng)初,維特根斯坦去找羅素,問(wèn)到:“你看我是不是一個(gè)十足的白癡?”羅素不知他為什么這樣問(wèn),維特根斯坦說(shuō):“如果我是,我就去當(dāng)一個(gè)飛艇駕駛員,但如果我不是,我將成為一個(gè)哲學(xué)家”。可見(jiàn)哲學(xué)這東西,只有真正的天才才有能力去研究它。
還好,我并不是要研究形而上學(xué),我只是要研究面向?qū)ο蟊澈蟮男味蠈W(xué)哲學(xué)基礎(chǔ)。
我也不是要證實(shí)這個(gè)哲學(xué)基礎(chǔ)的正確性與適用性。我只需要證明“面向?qū)ο蟊澈蟮哪莻€(gè)形而上學(xué)基礎(chǔ)是不正確的、是不適用于軟件開(kāi)發(fā)的。”
面向?qū)ο蟮膬纱蠛诵母拍钍牵?#8220;對(duì)象”與“類”。“一切皆是對(duì)象”是由樸素原子論而來(lái)的。“萬(wàn)物皆有類屬”就是由亞里斯多德的形而上學(xué)來(lái)的。
對(duì)于亞里斯多德的形而上學(xué)理論不熟悉的朋友,可以即時(shí)補(bǔ)課,中國(guó)人民大學(xué)哲學(xué)系的《西方哲學(xué)史》有好幾節(jié)專門講這個(gè)方面:《亞里斯多德的實(shí)體論I》、《亞里斯多德的實(shí)體論III》。還有就是到Google上去專門搜一下亞里斯多德的邏輯學(xué)說(shuō),看完以后,咱們回來(lái)接著說(shuō)。
咱們用自己的話說(shuō)一下:“種”、“屬”、“屬差”以及“定義”這幾個(gè)概念。
種:是一個(gè)大的概念,假設(shè)已經(jīng)預(yù)先定義好了的。
屬:所有屬于某一種的概念,都是那一種下面的屬。
屬差:同屬一種的、同一級(jí)別的屬之間的差別,或者說(shuō)個(gè)性。
定義:通過(guò)種加屬差,可以定義一個(gè)屬的概念。
舉例說(shuō)明:人是二足直立動(dòng)物。人是一個(gè)需要被定義的屬,動(dòng)物是人之所屬的種,二足直立是人作為動(dòng)物的共性之外,擁有的個(gè)性,也就是屬差。
懂得初步的面向?qū)ο缶幊痰耐緜儯銈兌伎闯鰜?lái)了吧,大多數(shù)OO語(yǔ)言也是這么定義類的。你定義一個(gè)Animal,再用Person去繼承Animal。在Animal里有一些屬性與方法,在Person里再加一些人所特有的。很多很多的面向?qū)ο蟮慕炭茣铮踔辆褪侵苯佑眠@個(gè)定義來(lái)舉的例子。
問(wèn)題出在哪里?或者有人會(huì)問(wèn):“這樣有什么不對(duì)嗎?”
我們可以通過(guò)“種+屬差”來(lái)定義一個(gè)新的屬嗎?定義成立的前提是什么?先要有種的定義。然后才可能有屬的定義。種的定義又是哪里來(lái)的呢?在一個(gè)種的概念之上,必然存在一個(gè)更普遍的種,一個(gè)更大的范疇。在亞里斯多德來(lái)說(shuō),在所有的種之上的種是“存在”,而存在是無(wú)法被定義的。而在面向?qū)ο蟮恼軐W(xué)里,即使是這一個(gè)最基本的哲學(xué)困境也被忽略了,無(wú)法被定義的概念,被代換為無(wú)需由程序員定義的概念(Object)。屬差的區(qū)別在哲學(xué)家看來(lái),是本質(zhì)的,是基于深刻認(rèn)識(shí)才能提出的。而在面向?qū)ο蟮恼軐W(xué)里,種的共性就是基類所定義的“屬性與方法”,而屬的個(gè)性,就是對(duì)于基類的擴(kuò)展。“種+屬差”變成了“公用代碼+擴(kuò)展代碼”。
當(dāng)概念定義這樣一個(gè)“問(wèn)題域的描述手段”,演變成“減少重復(fù)代碼原則”之后。Class繼承的概念就越發(fā)的模糊不清了。我們來(lái)總結(jié)一下:
1、面向?qū)ο笤韭暦Q的描述真實(shí)世界的目標(biāo),采用的工具卻是樸素的“種加屬差”的方式。
2、面向?qū)ο蠓治鲋校l(fā)現(xiàn)具體的對(duì)象還算是容易的,發(fā)現(xiàn)“種”的概念卻是困難的。
3、在實(shí)際應(yīng)用中,種概念的發(fā)現(xiàn)與定義,被偷換為公共代碼的抽取。
4、由于基類的定義的隨意性,導(dǎo)致子類不但可以擴(kuò)展基類的行為與特性,還可以覆蓋(改變)基類的行為與特性。
5、由于哲學(xué)概念的與開(kāi)發(fā)概念的混淆,使得在OO領(lǐng)域IS-A、Has-A、Like-A成為最為繞人的概念。
在寫完了哲學(xué)分析部分之后,我總算是喘了一口氣,仿佛穿越了最幽暗的深谷,終于走出了自己最不擅長(zhǎng)的領(lǐng)域了。
后來(lái)在MSN上和曹曉鋼聊了挺長(zhǎng)時(shí)間,對(duì)于OO的批判,他認(rèn)為有點(diǎn)過(guò)頭了。經(jīng)過(guò)我的解釋,他提出了一個(gè)更好的建議,清楚的說(shuō)明自己批判的OO,究竟是哪一個(gè)階段的OO,然后才不至于誤傷到已經(jīng)改善過(guò)后的OO。所以我打算整理一下對(duì)于OO發(fā)展階段的看法,寫在下面:
1、面向?qū)ο蟮恼Z(yǔ)言:先有語(yǔ)言
2、面向?qū)ο蟮姆治雠c設(shè)計(jì)理論:再有理論
3、面向?qū)ο蟮脑O(shè)計(jì)原則的全面總結(jié):再有原則
4、設(shè)計(jì)模式的初步提出:然后才有了真實(shí)的經(jīng)驗(yàn)總結(jié)
5、重構(gòu)方法的提出:然后才考慮到代碼設(shè)計(jì)的細(xì)節(jié)上的改善
6、AOP概念的提出:打破OO封裝的“封印”
7、新語(yǔ)言的出現(xiàn):Python、Ruby之類面向?qū)ο蟮膭?dòng)態(tài)語(yǔ)言:更加方便的語(yǔ)言?
8、ASM、CGLIB、Mixin之類技術(shù)的出現(xiàn):OO喪鐘的先聲
具體的對(duì)于各個(gè)階段的分析,將在隨后展開(kāi),目前對(duì)于OO的哲學(xué)分析,基本上是針對(duì)原始的OO概念的。隨后的OO技術(shù)的發(fā)展,也在試圖解決由于OO的哲學(xué)基礎(chǔ)假設(shè)帶來(lái)的問(wèn)題,當(dāng)然,越是解決問(wèn)題,也就離OO的本意越遠(yuǎn),現(xiàn)在有人還以為OO在不斷發(fā)展,而事實(shí)上,OO早就盛極而衰,目前已經(jīng)處在破產(chǎn)的前夜了,我的這篇文章,就是打算使這一天,早日到來(lái)!
類型系統(tǒng)
“事實(shí)上,我們猜想是,如果沒(méi)有知識(shí)表示和自動(dòng)推力工作的幫助,這些問(wèn)題(指類,繼承)是無(wú)法僅僅通過(guò)計(jì)算機(jī)語(yǔ)言設(shè)計(jì)的方式來(lái)處理的。”——SICP
2.5,中文版136頁(yè),角注118。
Elminster那篇論述,正好和我的文章形成一個(gè)互補(bǔ)關(guān)系,他以極為清晰的表達(dá)語(yǔ)言,說(shuō)明了OO打算以類型化方式描述真實(shí)世界,所面臨的難題。這也使我不必再次動(dòng)腦子思考如何回答JavaCup的哲學(xué)方面的疑問(wèn)了。而下面這一段話我想特別再次引用一下:
就我個(gè)人來(lái)說(shuō),比較傾向于認(rèn)為這條最終是走不通的死路,人是從事物的特征與屬性歸納出它的“類型”,而不是相反。某種意義上說(shuō),“類型”是為了節(jié)省描述時(shí)間而產(chǎn)生的 ……
唔,這個(gè)太遠(yuǎn)了,所以就此打住。
大家記住這段話中的,特征、屬性、類型這幾個(gè)關(guān)鍵字。我先繞個(gè)小彎再回到這個(gè)話題上來(lái)。
我之前分析的面向?qū)ο蟮恼軐W(xué)漏洞時(shí),也有不少朋友認(rèn)為,說(shuō):“面向?qū)ο蟛荒芎芎玫拿枋稣鎸?shí)世界,并非一個(gè)有意義的指控。OOA、OOD本來(lái)就是用來(lái)對(duì)需求建模的。也就是打算描述需求世界。”
其實(shí)我的指控分為兩個(gè)階段,一方面,OO所依據(jù)的哲學(xué)導(dǎo)致了軟件開(kāi)發(fā)的苦難,而且至今余毒未清。另一方面,即使是指打算對(duì)需求建模,OO的技術(shù)手段也是有缺陷的。
就這么說(shuō)吧:OO的類型系統(tǒng),原本是從ADT來(lái)的。一個(gè)抽象數(shù)據(jù)類型,將數(shù)據(jù)與操作封裝在一起,是出于對(duì)于數(shù)據(jù)被“莫名其妙的修改”的擔(dān)心。但是,結(jié)果呢,一個(gè)ADT如果不支持繼承,那么它的代碼就無(wú)法被重用。所以O(shè)O就在ADT的基礎(chǔ)上增加的一個(gè)繼承,通過(guò)繼承的方式來(lái)復(fù)用以有的代碼。這樣的思路原本沒(méi)有太大的問(wèn)題,如果它僅僅只想表達(dá)各種自定義數(shù)據(jù)類型的話。
但是在OO的哲學(xué)提出之后,一切皆是對(duì)象,所以一切出于類型體系之內(nèi),對(duì)于數(shù)據(jù)類型的定義,擴(kuò)展到了對(duì)于現(xiàn)實(shí)世界中各種實(shí)體的類型定義,整個(gè)一個(gè)類型系統(tǒng),其內(nèi)在的語(yǔ)義大大擴(kuò)展復(fù)雜化了。更糟糕的是——引用Elminster的話是從事物的特征與屬性歸納出它的“類型”——而因?yàn)镺O封裝也就是隱藏了內(nèi)部數(shù)據(jù),事物的特征與屬性,從其本質(zhì)屬性,被轉(zhuǎn)義為對(duì)外的提供的操作接口。但是,要分析一個(gè)實(shí)體的本質(zhì),而不是實(shí)體的外部表現(xiàn),更不僅僅是“我能對(duì)他做什么”。這才是實(shí)體分析有可能成功的關(guān)鍵,而在OO的語(yǔ)言設(shè)定中,這卻是難以做到的。
我們來(lái)看兩張圖片:

這是在SICP里討論類型系統(tǒng)的第一張圖片,我稱之為“OO成功案例”。

這是在SICP里討論類型系統(tǒng)的第二張圖片,我稱之為“OO失敗案例”。
為什么一個(gè)能夠成功,而另一個(gè)卻會(huì)失敗?以往的解釋其實(shí)比較“直覺(jué)”。看著這個(gè)圖,就想當(dāng)然的以為:“這是因?yàn)槎嘀乩^承導(dǎo)致的!”事實(shí)上呢?
第一張圖中所顯示的成功,很多人會(huì)認(rèn)為這是由于這一個(gè)對(duì)象塔中的每一個(gè)對(duì)象都能夠支持加減乘除運(yùn)算。而在幾何圖形中,這樣一致的操作接口不存在了。而事實(shí)上,正是因?yàn)閺?fù)數(shù)、實(shí)數(shù)、有理數(shù)、整數(shù),在本質(zhì)屬性上有一致之處,他們才能表現(xiàn)出一致的“可加減乘除性”。而不是相反。當(dāng)我們畫出第二張對(duì)象關(guān)系圖的時(shí)候,也不是根據(jù)幾何圖形可以接受的操作類型來(lái)進(jìn)行分類與顯示繼承關(guān)系的,而是根據(jù)不同的幾何圖形的本質(zhì)屬性的相近程度來(lái)劃分類型體系的。多邊形的本質(zhì)是:
“多條有限長(zhǎng)直線組成了一個(gè)封閉圖形”,而三角形與四邊形的本質(zhì)則是,邊的數(shù)量分別為三和四。等腰三角形的本質(zhì)是,不但邊的數(shù)量為三,而且其中有兩條邊的長(zhǎng)度相等,直角三角形的本質(zhì)是不但邊的數(shù)量為三,而且其中有一個(gè)直角。如此等等......
各位,請(qǐng)?jiān)俅嗡伎歼@樣的分類體系的內(nèi)涵。
我的結(jié)論是:“一個(gè)類型,是由其本質(zhì)決定了所能表現(xiàn)出的可操作性,而不是有其所能接受的操作決定了其本質(zhì)。然而,OO正好把這個(gè)問(wèn)題搞反了!”
繼承、重用、多態(tài)
OO的核心思想就是繼承,那么,OO為什么要繼承呢?對(duì)于這個(gè)問(wèn)題,OO的理論大師們有好多個(gè)版本的解釋:
1、“這是OO的類型系統(tǒng)自然的要求。設(shè)想一下生物學(xué)的分類系統(tǒng):動(dòng)物——>哺乳動(dòng)物——>靈長(zhǎng)類動(dòng)物——>人類。或者設(shè)想一下我們的概念系統(tǒng):機(jī)器——
>交通工具——>汽車——>小轎車。這樣的現(xiàn)象在你的生活中難道不是隨處可見(jiàn)嗎?”
2、“如果你有一個(gè)類,叫做車輛,這個(gè)車輛類能夠移動(dòng),現(xiàn)在你要建立一個(gè)子類,叫做家庭型轎車,你就可以直接繼承車輛這個(gè)類,而不需從頭再寫移動(dòng)部分的代碼了呀!”
3、“如果你有三個(gè)類,三角形、四邊形、正方形,他們都需要顯示在屏幕上,那么你就可以建立一個(gè)基類叫多邊形,提供一個(gè)draw()方法,然后讓那個(gè)三個(gè)類都繼承這個(gè)多邊形類并且覆蓋那個(gè)draw()方法,這樣,你就可以在繪圖的時(shí)候,統(tǒng)一對(duì)一種多邊形類進(jìn)行操作,不用去管那個(gè)對(duì)象究竟是哪一種多邊形。”
這三種解釋,是最為典型的OO繼承的好處的解釋了。但是你如果仔細(xì)的看,就能發(fā)現(xiàn)這三種好處,分別描述的是:“概念的特化”、“代碼的重用”以及“接口的重用”。或者可以分別命名為:“繼承”、“重用”、“多態(tài)”。
“這樣有什么問(wèn)題嗎?”,也許有人會(huì)問(wèn)。問(wèn)題就出在這三個(gè)好處是用一種方法提供,而這三個(gè)好處相互之間有時(shí)是相通的,有時(shí)又是矛盾的!當(dāng)我們運(yùn)用OO語(yǔ)言,來(lái)寫這樣的繼承的語(yǔ)句時(shí),一切都是“攪和在一起的”!
假設(shè)Class A有兩個(gè)屬性和兩個(gè)方法:String a1;int i;void f1();void f2();當(dāng)我們另外寫一個(gè)Class
B去繼承Class
A的時(shí)候,我們可以繼續(xù)使用某些屬性,而覆蓋另一些屬性,還可以繼續(xù)使用某些方法,而重寫另一些方法。還可以添加一些新的屬性,還可以添加一些新的方法。如果在加上各種訪問(wèn)控制符的限定與修正。誰(shuí)能夠告訴我:“這個(gè)Class
B究竟想干什么?!”
也許有人會(huì)繼續(xù)為這樣的現(xiàn)象辯解:“這是對(duì)于繼承的誤用,正確的OO程序不會(huì)這樣的!”
但是,這個(gè)辯解并不成立,幾乎所有的OO的編程語(yǔ)言,都沒(méi)有在繼承問(wèn)題上做出太多“非此即彼”的限制,原因只有一個(gè),那就是,在某些特定的場(chǎng)合,這樣的“拼盤”是相對(duì)最合理的編碼方式。
我們前面還沒(méi)有提到多重繼承,一個(gè)允許多重繼承的語(yǔ)言,會(huì)讓這個(gè)問(wèn)題更為復(fù)雜,也可以說(shuō)會(huì)使得場(chǎng)面越發(fā)的混亂。讓我們舉一個(gè)例子,這是Eiffel語(yǔ)言的繼承語(yǔ)法,讓我們看一看面對(duì)繼承這樣一件事情,一個(gè)程序員,究竟需要考慮多少問(wèn)題。來(lái)源是《對(duì)象揭密》,我就一邊抄,一邊直接翻成中文了。
繼承 :inherit 父類列表
父類列表 :{父類 ";" ... }
父類 :類名[特性適配說(shuō)明]
特性適配說(shuō)明:[Rename] :重命名以消除名字沖突
[New_exports] :重新設(shè)定特性導(dǎo)出的分組
[Undefine] :撤銷定義
[Redefine] :重定義以取代虛函數(shù)
[Select] :更加高級(jí)的功能
end
最值得看的就是這個(gè)特性適配說(shuō)明,更加深入的說(shuō)明還是各位自己去找書看吧。這就是號(hào)稱優(yōu)雅的解決了OO繼承問(wèn)題的Eiffel語(yǔ)言。他所謂的優(yōu)雅,可以不客氣的說(shuō),就是把所有的麻煩都明確的告訴你,而不是像C++和Java那樣,假裝什么事情都沒(méi)有發(fā)生過(guò)。但是,麻煩依然在那里,并沒(méi)有減少,根本的解決方法,是應(yīng)該不讓這樣的麻煩出現(xiàn)呀!可是OO確實(shí)無(wú)法做到這一點(diǎn)。
因?yàn)樗褦?shù)據(jù)和操作封裝在了一起,然后又偷換了實(shí)體本質(zhì)的概念,在這樣的情況下的OO,他的繼承是肯定搞不好的!
接口、泛型與重用
先說(shuō)點(diǎn)提外話,我從小學(xué)開(kāi)始學(xué)習(xí)BASIC和LOGO,到后來(lái)學(xué)習(xí)了FoxBase、FoxPro、C/C++、Visual
Basic、VBScript、JavaScript、PHP,之后才開(kāi)Java編程,之后也沒(méi)有再換過(guò)語(yǔ)言。誠(chéng)實(shí)的說(shuō),只有Java語(yǔ)言,是我認(rèn)認(rèn)真真的學(xué)習(xí)和研究的。對(duì)于面向?qū)ο蟮睦斫猓彩窃趯W(xué)習(xí)和使用Java之后,才真的開(kāi)始深入思考這方面的問(wèn)題,在此之前,我甚至認(rèn)為所有的語(yǔ)言都沒(méi)有什么本質(zhì)的差別,面向某某和面向某某之間也沒(méi)有什么大不了的差別。
所以當(dāng)我想要寫這篇文章的時(shí)候,其實(shí)內(nèi)心是相當(dāng)惶恐的,我對(duì)于面向?qū)ο蟮牧私猓鋵?shí)只來(lái)自于一種語(yǔ)言,那就是Java,而Java是不是就等于是面向?qū)ο竽兀慌率遣荒苓@么說(shuō)的吧。
JavaEye有人留言:“不要到時(shí)候說(shuō)不圓影響了一世英名。”;“討論這個(gè)問(wèn)題,我還是建議去看 SICP,你會(huì)發(fā)現(xiàn)所有OO具有的思想SICP都講到了”;“實(shí)際上我很懷疑莊某最后還是跑不出SICP的框架,如果是這樣,那么其理論的價(jià)值就要打折扣了。”我那個(gè)慌啊,趕緊到書店去買了SICP回來(lái)仔仔細(xì)細(xì)的啃,然后再在MSN上向T1好好的請(qǐng)教過(guò)幾次,最后總算放心了,我的思路,不在SICP的框架內(nèi),或者說(shuō),不在SICP作者的思考局限之內(nèi)。
還有人留言,提到了C++:“OO門檻較高是不爭(zhēng)的事實(shí)。的確很多人并沒(méi)有進(jìn)入。有句話可以套
用,沒(méi)有三年的功底,最好不要說(shuō)懂C++。幸運(yùn)的是這門東西的回報(bào),會(huì)告訴你所付出的是完全值得的。”我又慌了,C++背后的面向?qū)ο螅蔚雀呱睿覅s從來(lái)沒(méi)有用C++做過(guò)哪怕1000行以上的程序,這等門檻都沒(méi)有入的人,有資格評(píng)價(jià)面向?qū)ο竽敲创蟮氖虑椋s緊的,我又到書店去買了一本《對(duì)象揭秘》,我對(duì)于當(dāng)年gigix的一篇介紹《編程語(yǔ)言的宗教狂熱和十字軍東征》始終記憶猶新,里面提到了面向?qū)ο螅岬搅薈++的無(wú)數(shù)缺點(diǎn),還提到了Eiffel,一個(gè)據(jù)說(shuō)是比C++要好無(wú)數(shù)倍的面向?qū)ο蟮恼Z(yǔ)言。如果我要想加快速度,又想保證質(zhì)量的話,從《對(duì)象揭秘》里面應(yīng)該可以找出很多現(xiàn)成的彈藥吧。
抱著急于求成的功利主義目的,我開(kāi)始仔細(xì)看這本《對(duì)象揭秘》,一看之下,真是大有收獲:
*C++果然毛病多多,而且作為第一個(gè)大面積流行的OO語(yǔ)言,OO的實(shí)際含義更多的來(lái)自于C++。
*Java的毛病少了很多,因?yàn)樗氲囊恍└拍睿辉偈褂玫囊恍└拍睿蟠蟮臏p少了C++式OO編程的陷阱,只是這樣一來(lái),在復(fù)雜問(wèn)題上使用Java語(yǔ)言,往往會(huì)寫出很丑陋的程序。
*Eiffel同樣也是反思C++缺點(diǎn)的語(yǔ)言,但是它的改進(jìn)基本上是表面的,Java是使問(wèn)題簡(jiǎn)化,哪怕?tīng)奚Z(yǔ)言的表達(dá)能力,而Eiffel是使問(wèn)題表面化、集中化,陷阱雖然沒(méi)有了,但是問(wèn)題一個(gè)都沒(méi)有減少,反而因?yàn)?#8220;讓人眼花繚亂的復(fù)雜語(yǔ)法”,讓人望而卻步。
*《對(duì)象揭秘》是一本很一般的書,作者花了十多年的時(shí)間攢出一本書來(lái),實(shí)質(zhì)上還是BBS里一段一段討論的水平。
————————————————————————————————————————
好了,題外話結(jié)束,接下來(lái)討論正題,今天主要研究OO的概念中兩個(gè)較為邊緣的概念“接口”與“泛型”,以及探討一個(gè)實(shí)際上最為重要的誤用——“重用”。
1、關(guān)于“接口”
接口是什么東西?接口是一個(gè)很奇怪的東西!接口之所以奇怪,因?yàn)樗膩?lái)龍去脈實(shí)在是讓人看不懂。基礎(chǔ)的OO概念中,并沒(méi)有接口的位置,因?yàn)榘凑?#8220;經(jīng)典的”面向?qū)ο笏季S,一個(gè)沒(méi)有代碼、沒(méi)有內(nèi)部數(shù)據(jù),只有方法說(shuō)明的東西,是無(wú)法理解的。
追根溯源的說(shuō),首先是在C++的面向?qū)ο髮?shí)踐中,發(fā)現(xiàn)了對(duì)于“抽象類”的需要,為什么需要抽象類呢?因?yàn)榇a重用的需要,比如一個(gè)基類,其實(shí)就是一堆公用代碼,有一個(gè)名字把它框起來(lái)叫一個(gè)類,但是完全沒(méi)有道理把它實(shí)例化。像這種“發(fā)育不完全的類”,該拿它怎么辦呢?OK,就讓它不能“出生到這人世間來(lái)”。抽象類的本質(zhì)就是這個(gè)樣子的。
到了Java,因?yàn)閷?duì)于多繼承的恐懼,Java完全擯棄了多重繼承,這是Java被攻擊得最多的地方,因?yàn)檫@樣的單根繼承,實(shí)在是因噎廢食——因“怕被繼承體系搞亂”廢“更加方便道代碼重用”。于是Java就說(shuō)了:我們有一個(gè)“安全的”多重繼承,叫做“接口”,這個(gè)接口,完全沒(méi)有代碼,只有說(shuō)明,所以絕對(duì)安全,但是由能夠?qū)崿F(xiàn)多重繼承的好處云云。
而事實(shí)上呢?多重繼承的根本目的,并不是像Java所宣稱的那樣為了“同時(shí)繼承多種類型”,而是為了“同時(shí)重用多組代碼”。接口這一發(fā)明,完全不能達(dá)到多重繼承的代碼重用效果,卻被宣稱為多重繼承的替代品。其實(shí)質(zhì)是:“從一個(gè)發(fā)育不完全的實(shí)體,變成了一張徹底沒(méi)有發(fā)育的皮”。
最為令人感到奇怪的,還不是“接口的出現(xiàn)”,而是“面向接口編程”的出現(xiàn),Java被冠以“面向接口的語(yǔ)言”的美名,“面向接口設(shè)計(jì)”成了OO的設(shè)計(jì)原則之一,“針對(duì)抽象,不要針對(duì)具體”,成了OO名言之一。實(shí)在是......
關(guān)于OO的設(shè)計(jì)原則,我下面還會(huì)專門討論,這里先指出一個(gè)大漏洞:“抽象類的那個(gè)抽象,和與具體相對(duì)的那個(gè)抽象,根本就不是一回事!”
繼承、多態(tài)與泛型沖突的一個(gè)例子
寫技術(shù)文章,例子其實(shí)很難舉,特別是找到有殺傷力的,決定性的例子,相當(dāng)困難。昨天我接著看《對(duì)象揭密》,總算被我找到一個(gè),當(dāng)然,它那上面的解說(shuō),實(shí)在是比較模糊,因此我決定用自己的話重新敘述一遍,代碼示例用Java的泛型語(yǔ)法,但是要表達(dá)的意思,確實(shí)所有的具有泛型的OO語(yǔ)言都需要面對(duì)的。
java代碼:
public class X {
protected int i;
public void add(int a){
i=i+a;
}
}
public class Y1 extends X {
public void add(int a){
i=i+a*2;
}
}
public class Y2 extends X {
public void add(int a){
i=i+a*3;
}
}
這是三個(gè)最簡(jiǎn)單的類,Y1和Y2都繼承了X,并且重寫了add函數(shù)。當(dāng)然,這只是舉例,實(shí)際上這三個(gè)add中,有兩個(gè)是不合理的。
java代碼:
ArrayList listx=new ArrayList();
ArrayList listy1=new ArrayList();
ArrayList listy2=new ArrayList();
listx.add(new X());
listx.add(new Y1());
listx.add(new Y2());
listy1.add(new Y1());
listy2.add(new Y2());
這幾行代碼都非常簡(jiǎn)單,只是為了說(shuō)明一個(gè)道理,在ArrayList和ArrayList中,能夠放的就只有Y1和Y2了,而在以X為泛型的ArrayList中,就可以放X、Y1、Y2了。而當(dāng)然了,這樣的用法,只怕是不合泛型的目標(biāo)的,本來(lái)就是希望能有一個(gè)類型的自動(dòng)檢查與轉(zhuǎn)換,都放在ArrayList中,幾乎就等于都放在ArrayList中了。
現(xiàn)在我們有這樣一個(gè)需求,對(duì)于得到的ArryaList,能夠一一調(diào)用里面的對(duì)象的add(int a)方法,當(dāng)然了,只要這個(gè)ArrayList里的對(duì)象都是X或者X的子類就行了。我們可以寫出這樣的代碼:
java代碼:
public void addListX(ArrayList listx){
for(int i=0;isize();i++){
X x=listx.get(i);
x.add(1);
}
}
是不是很簡(jiǎn)單?且慢,這個(gè)addListX函數(shù),我們能夠把listx傳遞給它,但是能不能把listy1和listy2
也傳遞給它呢?如果我們能夠把listy1和listy2傳遞給它,就相當(dāng)于執(zhí)行了如下的類型轉(zhuǎn)換代碼:
java代碼:
ArrayList listy1=new ArrayList();
ArrayList listx=listy1;
這樣做行不行呢?在Java和C++中,是不行的。也就是說(shuō),如果我們要想只寫一遍addListX這樣的函數(shù),而不用再多寫兩遍addListY1();addListY2();這樣的函數(shù),就需要把所有的X,Y1,Y2這樣的類型都放到ArrayList這樣的容器里,否則,addListX函數(shù),是不接受ArrayList和ArrayList類型的。即使Y1和Y2是X的子類型,ArrayList與ArrayList也毫不相干。不能相互轉(zhuǎn)換。
有人也許會(huì)說(shuō),為什么這么限制嚴(yán)格呢?Eiffel就沒(méi)有這么這么嚴(yán)格的限制,他允許ArrayList自動(dòng)轉(zhuǎn)型為ArrayList,這樣是好事情嗎?如果listy能夠被轉(zhuǎn)型為ArrayList,那么就可以往里面添加Y2類型的對(duì)象了,這又是原來(lái)的泛型ArrayList不允許的。也就是說(shuō):除非addListX能夠保證只對(duì)listy1做只讀操作,否則,類型安全性這個(gè)泛型原本要追求的目標(biāo)就不能實(shí)現(xiàn)了。而如果要追求絕對(duì)的類型安全性,像C++和Java那樣,那么代碼要么就得寫三遍,要么X、Y1、Y2類型的對(duì)象就得都放到ArrayList這樣的泛型容器里去。
注意看這其中的左右為難的狀況,繼承、多態(tài)、泛型,并不是真正正交的、互不干擾的,而是在一個(gè)相當(dāng)普通的目標(biāo)面前,他們就起了沖突了。