Thinking in Delphi:語言的變革

Thinking in Delphi:語言的變革

Delphi Language的誕生

2003年11月,Borland公司正式發佈了Delphi的最新版 本:Delphi 8 for Microsoft .NET Framework。如它的名字所揭示的,Delphi 8不再支持Win32平臺下的開發,而是完全基於Microsoft .NET Framework。毫無疑問,這爲Delphi帶來了革命性的變化,而讓一個計算機編程語言的愛好者最感興趣的地方,則莫過於Delphi 8對語言本身的改進。 衆所周知,Delphi將OO的思想帶入傳統的Pascal中,從而創造了著名的Object Pascal語言。從Delphi 1以來,Object Pascal一直在不斷的改進中。但是在從Delphi 7升級到Delphi 8的過程中,不但編程模型發生了根本性的變革,語言本身也得到了大幅度的擴展和改進,結果就是導致了幾乎是一種新的語言的誕生。在本文中,將Delphi 8中使用的語言稱爲Delphi Language,與以前的Object Pascal相區別。

從Delphi 7開始,Borland就已經將Object Pascal稱爲Delphi Language。但是Delphi 7中的語言與以前版本相比並沒有根本性的改變,這一改變實際上是發生在Delphi 8中的。因此,在本文中,將Delphi 8中使用的語言稱爲Delphi Language,以前版本的Delphi中使用的語言則稱爲Object Pascal。 Delphi Language增加了對如下特性的支持:名字空間(Namespace)、嵌套類(Nested Type)、類靜態方法(Class Static Method)和類屬性(Class Property)、對記錄(Record)類型的增強、密封類(Sealed Class)和Final方法(Final Method)、多播事件(Multicast Events)機制、運算符重載(Operators Overload)、裝箱/拆箱(Boxing/Unboxing)機制,等等。 不同的編程語言之間的比較是一件非常容易帶來爭議的事情。但是,在許多情況下,這種比較看起來似乎是不可避免的。尤其是在編程語言領域出現革新時,語言之間的橫向比較往往能夠讓人更清楚地看到這種革新的本質。因此,當Delphi Language面世時,將它與其它的主流編程語言進行比較就成爲了一種誘惑。在下文中,作者將進行這種比較。 與Delphi Language進行比較的語言的挑選標準是這樣的:

1、必須是通用的語言而不是專用的語言。 2、必須是強類型的、編譯或半編譯的語言,而不是解釋執行的腳本語言。 3、必須是完全支持面向對象編程(OOP)的語言(這樣就將C這樣的語言排除在外,雖然C現在仍然是最主流的工業語言標準之一)。 這種挑選的方式也許並不合適,但是,這種比較僅僅表明作者在某個特定的角度進行觀察時得到的結論,而不是試圖對這些語言作出優劣之分。而且,相信許多讀者也會贊成這一點,即將應用範圍和實現方式完全不同的語言進行比較,在很大程度上是沒有意義的。根據這個標準,C++、Java、C#成爲中選者,分別代表了Java、.NET和原生代碼(Native Code)三個主要平臺下的編程語言。Visual Basic .NET由於和C#過於相似,作者認爲沒有將它增加進來的必要。Delphi Language的前身Object Pascal也被加入以作爲參照。

語言特徵

誠如有人指出過的,語言之間的比較很容易受到主觀的影響,因此作者儘量將比較的範圍限制於實證性的語言特徵的層次上,對於這些問題基本上不會存在多少爭議。表1列出了這幾種語言的比較,Y表示是,N表示否。

表1 實特徵的語言特徵對照表

注1:Java通過包裹類(Wrapped Class)來完成基本類型和對象之間的轉換,而.NET則在語法層次提供支持,並且這一功能內建在虛擬機的指令集中。因此,只能說Java部分地支持 Boxing/Unboxing。但是,有消息說Java將在以後的版本中對Boxing/Unboxing機制提供類似.NET的支持。C++和 Object Pascal則完全不支持Boxing/Unboxing。 注2:Sun公司已經宣佈將在JDK 1.5中支持泛型編程(Generic Programming)。Microsoft也宣佈將在下一個版本的C#中支持Generic Programming。關於這方面的討論已經非常豐富。在2003年11月的Borland Conference上,Delphi的開發小組也宣佈了類似的計劃。 注3:Object Pacsal通過內置RTTI可以支持對不確定對象的屬性和方法的檢索,這也是Delphi組件機制的基礎之一。但是Object Pacsal不支持動態生成類,因此只能說部分支持Reflection。

如同你可能想到的,這個表遠遠不夠完備,即使在列出的這些特徵中,有很多情況也不是僅僅用“是”或“否”可以總結的。這一點,從表後幾個有點冗長的註解就可以看出來。 事實上,還有一些主要的區別沒有在這張表中列出來。由於它們是如此複雜,以致於不可能簡單地用“是”或“否”來放在一張表中,而必須專門進行討論。

運行平臺

從表面上看來,編程語言與運行其上的平臺之間並沒有必然的聯繫,但事實並非如此。下面是這幾種語言和運行平臺的對應關係:

表2

C++之父Bjarne Stroustrup博士說過一句非常有知名度的話:“Java並不是跨平臺的,Java自己就是平臺。”無論前一句的結論是否合適,後一句卻是確鑿無疑的。因此,在這裏將JVM列爲一個平臺。按照同樣的邏輯,Delphi Language和C#的運行平臺是.NET CLR。 從上表中,我們可以發現這樣一種現象,即Java和C#這兩種純粹的面向對象的語言都是基於虛擬機的。這就是說,這些運行於虛擬機之上的語言都不支持全局變量和全局方法。而Object Pascal和C++這兩種多風格的編程語言則運行於原生平臺之上。對這一現象進行思考以後,也許可以得出如下結論:現在所有的主流操作系統,包括 Windows、Linux和Unix,底層都是由C或者彙編語言開發,提供的API都是C風格的。因此在原生平臺上運行的編程語言必須支持C風格的全局變量和全局方法,而虛擬機卻可以繞過這一限制,提供純粹的面向對象的編程模型。 一個有趣的趨勢是,Microsoft通過將操作系統從Win32向.NET的轉移,正在逐漸地接近純粹的面向對象的操作系統這一目標。 這一結論的一個重要的例外就是Delphi Language。它象Java和C#一樣,運行於虛擬機之上,但是它仍然支持全局變量和全局方法,換句話說,仍然支持面向過程的編程風格。Delphi Language之所以提供這一特徵,應該是爲了向後兼容性考慮,因爲畢竟有大量的Object Pascal代碼在開發和維護中,它們可能需要過渡到.NET平臺,而且存在着大量熟悉Object Pascal語法的程序員,他們希望以儘可能小的成本轉移到.NET平臺。 但是,這一現象的出現,說明了一個問題,那就是 .NET對編程語言的統一併沒有人們猜想的那麼嚴重。在.NET的CLS發佈以後,人們曾經猜想.NET平臺下所有的編程語言都長得一個樣子。這一觀點在Microsoft自己支持的4種語言(Visual Basic.NET、C#、Visual J#、Managed C++)中可能是正確的,但對於其它的語言就未必正確了。從理論上來說,任何語言只要滿足CLS規定的最小規則集,就可以支持.NET,但CLS並沒有限制這些語言進行的擴展,只要它們的編譯器能夠生成規範的 .NET代碼即可。Delphi Language就是一個這樣的例子,它在一個純粹的面向對象的平臺上保留了使用了面向過程編程的能力。另一個例子是Eiffel#,它通過強有力的編譯器在.NET平臺上實現了多重繼承。 運行平臺對編程語言的另一影響是內存自動回收(Garbage Collection)機制。JVM和.NET CLR這樣的虛擬機都具有內存自動回收機制,因此對相應的Java、C#和Delphi Language這三種語言都產生了影響,尤其是在對象銷燬方面。而運行於原生平臺上的C++和Object Pascal則不具有這一機制,因此程序員必須自己實現內存的回收。是的,通過某些第三方庫,C++和Object Pascal也可以實現內存自動回收,但這並不是語言本身的特徵。

容器類

和容器(Container)打交道是開發人員日常編程中最經常遇到的工作,優秀的容器支持,對一種編程語言的推動作用,怎樣估計也不會過分。 在C和Pascal的時代,除了數組(Array)這種最簡單的容器之外,沒有提供其他的支持。程序員必須自己實現象鏈表(List)這樣的最基礎的數據結構。 在Object Pascal中,這種情況得到了一些改善。Object Pascal提供了一些類作爲容器,實現基本的數據結構,如TList是列表的實現,TQueue是隊列的實現,TStack是棧的實現,TBucketList是哈希表(Hash Table)的實現。這些類將無類型指針而不是對象作爲容器的元素,因此程序員必須自己負責進行類型轉換和處理由此可能產生的問題,但另一個較爲有益的後果是可以同時將基本類型和對象裝入容器中,而無須進行Boxing/Unboxing的轉換。從Delphi 6開始,增加了以對象爲元素的容器類,例如TObjectList、TObjectQueue、TObjectStack和 TObjectBucketList。在實際編程中,這些容器類非常具有實用性,但是,它們仍然只是一系列扁平的、不便於擴展的類,並不意味着一種基於面向對象思想的普遍的解決方案。 在C++中,對容器的支持得到了空前的改善。在模板(Template)和操作符重載(Operator Overload)技術的基礎上,C++發展出了一整套標準模板庫(Standard Template Library, STL)。STL提供了大量的容器類,既可以使用對象作爲元素,也可以使用指針作爲元素。同時,在函數對象(Function Object)技術的基礎上,STL還提供了許多獨立於容器類型的泛型算法,用於對容器中的元素進行各種操作。STL是如此強大和複雜,以至於出現了許多專門的著作對它進行討論。但是,正因爲如此,STL在方便開發工作的同時,也對開發者的水平提出了更高的要求。程序員們不得不花費大量的時間用於熟悉和使用STL,而事實上,大多數程序員在工作中遇到的需求,可能只佔STL提供的功能的一小部分。 在Java中,效率和複雜程度達到了某種均衡。Java中的容器類完全以對象作爲元素,在處理的方式上相當一致,但是由於容器類僅僅操縱 Object對象,程序員仍然必須自己處理向上造型(Upcast)的問題。另外,基本類型必須通過編程轉化爲包裹類的對象,才能夠被容器類所處理。除去這些問題以外,Java提供了一套規模適中、實用性極強的容器類。從Java 1.2開始,對以前版本的容器類進行了較大幅度的改進,最終形成了我們現在看到的這個樣子。Java的容器類的規模、複雜程度和精巧性,正好使得程序員不需要象對待STL那樣花費太多的時間進行學習和掌握,但又足以能夠應付絕大多數常見的需求,不需要程序員象在C語言中那樣頻繁地重新實現自己的容器。實踐證明,Java的容器類是非常成功的。

C#和Java一樣,也提供了自己的容器類。目前C#的容器類的規模還無法與Java相比,但是它的風格與Java有明顯的不同。這些容器類事實上是.NET的FCL中提供的。由於Delphi Language與C#使用同一套FCL,因此它的容器類與C#基本相同。

語言的演變

讓我們回到表1,看看這張表格所列舉的差異能夠說明些什麼。從時間順序上來看,從C++,到Object Pascal,到Java,再到C#和Delphi Language,可以看出編程思想和風格(但並不表示優秀程度和實用性)的演化。 C++是這些語言中出現得最早的,同時它也提供了最好的向後兼容性:幾乎全兼容面向過程編程時代的C語言。從市場策略來看,這是非常有遠見的舉動,C++成功地以C語言的後續者出現,但是同時也導致了許多嚴重的問題。對於這一點,C++的發明者也並不諱言。C++支持完全的面向對象編程,支持名字空間(Namespace),支持嵌套類型(Nested Type),這使得它對大規模程序的開發提供了很好的支持(這也是C++的設計目標之一)。但是,C++也保留了全局變量/全局函數、Structure 等語法,這在獲得兼容性的同時,犧牲了面向對象的特性(當然,C++的目的並不是純粹的面向對象,而是多風格編程)。另外,C++在RTTI上完全無法與後來的幾種語言相比。但是,C++還提供了複雜的模板和操作符重載機制,從而全面支持泛型編程(Generic Programming),這一點目前在這些語言中是獨一無二的。總的說來,C++是一門龐大的、複雜的、博大精深的語言,它在從面向過程編程向面向對象編程的過渡中,最大限度地保持了向後兼容性,同時也付出了沉重的代價。C++在許多前沿領域,都進行了有益的探索,這些探索的成果被其它語言所汲取,但也使得C++的學習門檻過高。 Object Pascal是Anders Hejlsberg對傳統的Pascal進行擴展以後發明的語言,它誕生的背景是面向對象編程思想的流行和Windows操作系統的崛起。針對這兩個潮流,Object Pascal做了很多的優化以適應它們。 1、Object Pascal支持完全的面向對象編程,而同時的Visual Basic等競爭對手只支持基於對象的編程(Object-Based Programming)。 2、Object Pascal實現了在委託(Delegate)基礎上的事件機制,完整地封裝了Windows事件處理過程,這使得它對Windows GUI程序的開發提供了極好的支持。 3、Object Pascal大幅度地強化了RTTI的能力,並在RTTI和PME(Property,Method,Event)模型的基礎上實現了自己的開放性的Component機制,使得Delphi成爲一個強大的組件化開發工具。 但是,Object Pascal的缺點也是相當明顯的,這些缺點在長期的使用過程中逐一地暴露出來了。 1、Object Pascal保留了全局變量/全局函數、Record等語法。和C++一樣,這些特徵提供了向後兼容性,保留了面向過程編程的風格,也許還獲得了效率上的好處。但是,它們同樣也破壞了面向對象的特性,很容2、易造成編程風格上的混亂(從理論上說,這一點不應引咎於編程語言,但這確實是常見的情況)。 3、和C++不一樣,Object Pascal並不是一開始就是以大規模的程序開發爲目標進行設計的。因此它一直不支持名字空間和嵌套類型等特性,而這些特性對於大規模的開發是相當重要的。 Object Pascal的事件機制與Windows捆綁得過於緊密,當Object Pascal試圖越過Windows GUI程序的開發,進入其它的領域時,開發者們或多或少地遇上了這些問題。 Java則是一種純粹的面向對象編程的範例。“一切都是對象”是Java提出的並盡力予以貫徹的口號。和C++與Object Pascal不同,Java雖然在語法上盡力保持C的風格,但它並沒有向後兼容的歷史負擔。Java在語法上也提供了對Interface等高級特性的支持。在這一切的基礎之上,Java達到了無與倫比的明晰性,以及更濃厚的“OOP”的氣息。和C++一樣,Java一開始就被設計成一種承擔大規模程序開發的語言,因此它對名字空間和嵌套類型的支持非常完善。另外,Java支持基於RTTI技術的反射(Reflection)機制,在這一點上已經超越了 Object Pascal。因此,Java在組件化方面做得非常成功(JavaBean就是成果),這又導致了Java在分佈式計算技術上的成功。在過去的幾年中,隨着網絡的崛起,Java獲得了空前的成功。 但是,Java並不是沒有改進餘地的。.NET/C#的出現有力地證明了這一點。 相比Java,C#最大的改進可能就是在效率方面。和Java一樣,C#是一種純粹的面向對象的編程語言,“一切都是對象”。對於Java中的一些優秀的語言特徵,例如單根繼承結構、Interface和Reflection等,C#也照單全收。但是,C#至少在以下幾點上跟Java有着顯著的不同:

C#大幅度地提高了值類型(Value Types)的重要性。值類型是和引用類型(Reference Types)相對應而言的。在處理大對象的情況下,引用類型比值類型更有效率,對於小型的、頻繁的對象創建,使用值類型遠比引用類型更有效率。在Java 中,除了基本類型以外,所有的變量都是引用類型。C#中的值類型包含基本類型和Struct,它們都派生自System.ValueType,可以統一地被作爲對象處理。和Class的區別在於,Struct是一種值類型,在棧中創建,而且是隱式地封閉的(Sealed),因此比Class更有效率,可以作爲一種輕量級的Class來使用。 C#提供了一些和OOP沒有直接關係,但在實際應用中非常有用的語法特徵,例如對枚舉(Enum)、委託(Delegate)、屬性(Property)、預處理器(Preprocessor)和多播事件(Mutilcast Events)的支持。另外,C#還引入了一些C++支持而Java不支持的特性,如運算符重載。 在Java中,所有的類都是可以被繼承的。C#提供了封閉類(Sealed Class)的功能。封閉類不能夠被繼承。封閉類可以使軟件架構更爲嚴謹,並且在運行期更有效率。 C#提供了對不安全代碼(unsafe code)的支持。在不安全代碼中,C#提供了更加低層的能力,可以使用C風格的指針(Pointer),直接對內存進行操作。它的代價是失去了許多虛擬機上的特性,包括內存自動回收,類型安全,數組越界檢查。 可以明顯地感覺到,C#和Java二者在很多方面相似,但C#比Java更偏向實用性。無論是對值類型的重視,對屬性、委託和預處理器的支持,還是對不安全代碼的支持,都是跟OOP沒有直接關係、甚至破壞OOP的“純潔性”(例如預處理器)、但是在實際編碼中又非常有用的特徵。相比之下,Java顯得更學院派,整體風格更爲純粹,或者說更爲理想主義。對這二者的取捨,可能存在着不同的意見,但是無可置疑的是,在付出某些付價之後,C#確實在效率上和簡潔性上超過了Java。 Delphi Language是比C#出現得更晚的語言,也是我們進行比較的語言中和C#最爲相似的語言。由於CLS的規定,上述C#的特徵幾乎都可以適用於 Delphi Language。但是,與C#不同的地方是,Delphi Language必須顧及與原來的Object Pascal的兼容性問題。 相對於Java和C#,Delphi Language的特徵可以總結如下:

Delphi Language保留了原有的.pas文件格式,也就是由Interface和Implementation組成的格式,這意味着類的聲明和定義是分離的。這種風格可以追溯到C和C++。在Java和C#中,類的聲明和定義不再分開了。 Delphi Language保留了全局變量和全局函數。.NET是一個純粹的面向對象的編程模型,C#也是純粹的面向對象的語言,但是Delphi Language裏面仍然保留了對全局變量和全局函數和支持,也就是仍然保持了面向過程編程的風格。 Delphi Language支持MetaClass,這是Object Pascal語言中的類引用(Class Reference)的一種變形。類引用是Object Pascal獨有的特徵,Object Pascal通過類引用保存類的原始信息,並據此實現多態和RTTI等功能。.NET CLS中沒有與類引用相對應的功能,因此Delphi Language引入了MetaClass的概念,通過編譯器隱式地爲每一個類加入一個嵌套類,在這個嵌套類中保存着類的原始信息。當需要類引用時,就從嵌套類中獲取有關信息。 Object Pascal,或者說Pascal的嚴謹和強類型的風格,在Delphi Language中也得到了體現。在C#中,Boxing/Unboxing可以通過直接在不同類型之間進行賦值來執行,但在Delphi Language中,必須通過更爲嚴謹的方式,即顯式造型來完成。 總結這些特徵,我們可以得出一個大致的印象。由於必須符合.NET CLS的規範,Delphi Language進行了較大的擴展,支持許多高級的OO特性,從而使它在語言層次上已經達到了與Java和C#同樣的水平。前文中已經說過,相比 Java,C#更傾向於實用性,在這一點上,Delphi Language比C#有過之無不及。造成這一點的原因有兩個,一個是因爲Delphi Language不象C#那樣是一種完全重新發明的語言,它必須照顧到向下的兼容性;另一個是因爲Delphi Language,或者說Object Pascal,從一開始就是一種針對實際應用的語言,它的目標一直是如何方便而有效地滿足用戶需要,而不是符合某種特定的理論。

結論

C++的直接前驅是C語言,C是一種靈活性極大的語言,與硬件層關係緊密,因此被稱爲“可移植的彙編語言”,也被稱爲“中級語言”(與彙編這樣的低級語言,以及Basic、Pascal這樣的高級語言相對而言)。C提供了對彙編的一種抽象,使得不同平臺之間的C語言的互相移植成爲可能。同時,它的面向過程的特徵,提供了模塊化編程的能力,從而有利於大規模的軟件開發工作。 C++將面向對象編程和泛型編程的思想引入了C語言,這些特徵分別來自Smalltalk和Ada等語言。這些特徵的加入,使得C++成爲比C語言更高層次的抽象。這種抽象使得C++具有更高程度的移植性,也更適合開發大規模的軟件。但同時,由於C++需要向下兼容C,使得它殘留了許多C的不好的特徵。C++的出現,可以視爲C向更高層次的抽象的不那麼徹底的嘗試。 繼C++之後,Object Pascal作爲Pascal進行的同類嘗試的產物出現了。Object Pascal針對C++的成功與失敗之處進行了改進,改善了OO的機制,拋棄或簡化了其中某些過於複雜或不貼近實用的設計,並在此基礎之上建立了自己的 IDE和組件機制。相比C++,Object Pascal並沒有試圖進行進一步的抽象,但是作爲一種輕量級、實用性極強的語言,它在應用領域獲得了極大的成功。 Java的誕生比Object Pascal略早,但是與Object Pascal相反,Java正是向更徹底的抽象努力的產物。Java通過虛擬機使它徹底地獨立於任何平臺,通過語法和API的強制貫徹了完整的OO思想,通過內存自動回收機制將原來由程序員完成的工作交給了虛擬機本身。Java徹底地改變了編程模型。由於Java是如此激進,因此它在誕生的前幾年並沒有得到普遍的接受,隨後又由於同一原因獲得了空前的成功,這可能是它的發明者自己也沒有預料到的。 但是,歷史並未就此終結。C#的出現,表示着對這一努力的某些過激之處的矯正。C#與Java在許多方面非常近似,但是,C#重新將實用性而不是理論放在第一位。對值類型的重視,對枚舉、委託、屬性、運算符重載、預處理器等特徵的支持,都使得C#充滿了更多的C++或Object Pascal的氣味。這是從Java的抽象層次往後退了一步。 Delphi Language的特徵與C#非常類似,如果說有區別的話,那就是Delphi Language更注重實用性,保留了許多與Object Pascal兼容的特性,因此位於更低的抽象層次上。如果將C/C++和Java看做兩個極端,那麼C#就位於這兩個極端中間,而Delphi Language又位於C#和C/C++中間。如下圖所示:

我們也許可以作出如下結論:近年來計算機編程語言的演變,是一個從具體到抽象、再從抽象到具體有所迴歸的過程。編程語言本身就是人們企圖對計算機硬件進行抽象的產物,隨着軟件的規模越來越大,編程語言也越來越複雜,抽象的層次越來越高。面向對象編程理論和泛型編程理論都是這種努力的成就,它們能夠幫助編程語言達到更高的抽象層次。但是,從哲學的角度來說,人類的理性有其極限。試圖用某種統一的方式對整個世界進行全面的解釋的努力,固然是一種誘惑,但最後都不能避免失敗的命運。抽象的層次越高,規範越嚴格,就會離硬件越遠,犧牲越多的效率,最終不但遠離了硬件,甚至也已經偏離了人類的自然思維方式,偏離了抽象原本要達到的目標。在Java達到了某種抽象的極限之後,C#和Delphi Language的出現是對這一理想化的傾向的反動。

轉自[http://www.chinadforce.com/viewthread.php?tid=889286]

相關日誌