![]() |
![]() |
級別: 中級 Bruce Tate (bruce.tate@j2life.com), 總裁, RapidRed 2006 年 12 月 18 日 靜態(tài)類(lèi)型語(yǔ)言(如 Java? 語(yǔ)言和 C)可以在編譯時(shí)把方法調用綁定到其實(shí)現中。這項策略讓這類(lèi)語(yǔ)言可以執行更豐富的語(yǔ)法和類(lèi)型檢查。比起不具有此項編譯時(shí)檢查功能的動(dòng)態(tài)類(lèi)型語(yǔ)言來(lái)說(shuō),靜態(tài)類(lèi)型語(yǔ)言更加穩定且具有更佳的性能。然而靜態(tài)類(lèi)型語(yǔ)言存在一個(gè)嚴重的局限性:前期綁定。一些動(dòng)態(tài)類(lèi)型語(yǔ)言(如 Ruby、Smalltalk 和 Self)允許延遲綁定,它們可以實(shí)現另一個(gè)層次的編程功能。 幾年前,我有幸教我的大女兒學(xué)滑雪?;W(xué)校提供的工具里有一條繩子,用這條繩把雪橇的尖端綁在一起。利用這根繩,初學(xué)滑雪的人能夠輕易地實(shí)現較為理想的滑雪動(dòng)作,如轉彎、減速和停止。最初,這些滑雪者十分依賴(lài)于這條繩子。我女兒還發(fā)誓說(shuō)她離開(kāi)這條繩就不滑雪。當然,她這樣說(shuō)是因為她剛剛開(kāi)始學(xué)所以對整個(gè)過(guò)程不了解。這沒(méi)關(guān)系。因為我知道將來(lái)她最終會(huì )在滑雪時(shí)迫使自己沖破這一束縛。
作為一名 Java 開(kāi)發(fā)人員,我也有過(guò)類(lèi)似的經(jīng)歷。我喜愛(ài)靜態(tài)類(lèi)型提供的安全性。我曾和 Dave Thomas 就靜態(tài)類(lèi)型檢查的優(yōu)點(diǎn)這個(gè)問(wèn)題進(jìn)行過(guò)辯論,但我卻不能被說(shuō)服。如果沒(méi)有最初的這條“安全之繩”,滑雪會(huì )是一種截然不同的體驗。對于許多人來(lái)說(shuō),靜態(tài)類(lèi)型也是一種依賴(lài),這和雪橇上的束帶沒(méi)有什么區別。Dave 認為我只是還沒(méi)有足夠的經(jīng)歷來(lái)理解動(dòng)態(tài)語(yǔ)言的好處罷了。當我認識到 Ruby 和 Smalltalk 的妙處之后,我開(kāi)始明白對動(dòng)態(tài)類(lèi)型我的確了解得不夠,但這也加深了我對它的理解(參閱之前的 跨越邊界 系列,獲取對靜態(tài)類(lèi)型策略和動(dòng)態(tài)類(lèi)型策略的總體比較)。對我來(lái)說(shuō)它最大的好處是延遲綁定。本文使用 Ruby、Smalltalk 和一個(gè)叫做 Self 的 Smalltalk 的派生語(yǔ)言的編程例子探討了延遲綁定的好處。 后期綁定和前期綁定 編程語(yǔ)言能夠將對函數(或在面向對象語(yǔ)言中的方法)的聲明從其調用中分離出來(lái)??梢月暶饕粋€(gè)方法并使用單獨的語(yǔ)法調用這個(gè)方法,但最終系統需要將這兩者綁到一起。將調用和實(shí)現綁到一起的過(guò)程叫做綁定。前期先綁定到類(lèi)型再綁定到實(shí)現,還是后期先綁定到類(lèi)型再綁定到實(shí)現,這對一門(mén)給定語(yǔ)言的編程體驗來(lái)說(shuō)有著(zhù)顯著(zhù)的影響。大多數面向對象的語(yǔ)言都在后期綁定到實(shí)現,從而允許多態(tài)性 ,該功能讓您能夠將許多不同的子類(lèi)型表示為一種類(lèi)型。Java 代碼和 C 主要在前期的一個(gè)編譯的步驟里綁定到一種類(lèi)型。使用此策略,編譯器就有足夠的信息可以捕獲許多不同類(lèi)型的 bug,比如說(shuō)方法參數或返回值之間類(lèi)型的不兼容。例如,清單 1 中的代碼就會(huì )產(chǎn)生幾個(gè)編譯錯誤: 清單 1. 編譯時(shí)綁定
通過(guò)編譯時(shí)捕獲錯誤,前期綁定有助于在提早檢查出問(wèn)題。對這種協(xié)助的價(jià)值存在很多質(zhì)疑。很明顯,類(lèi)型檢查并不足以證明代碼的正確性。必需經(jīng)過(guò)測試,而這種測試所發(fā)現的問(wèn)題也常常與類(lèi)型有關(guān)。但這種協(xié)助是有代價(jià)的。使用前期綁定,編譯器必需對目標對象的結構作出如下假設:
這些假設所帶來(lái)的限制在最初也許并不明顯,但如果您再深入研究一下,很快就能發(fā)現后期綁定的益處,其中一些在跨越邊界 系列中已經(jīng)介紹過(guò):
連續區間 靜態(tài)或動(dòng)態(tài)只是連續區間中的點(diǎn)。一些語(yǔ)言高度靜態(tài)。Java 語(yǔ)言比 C 或 C++ 更為動(dòng)態(tài)。連續區間中的每個(gè)點(diǎn)都有一套自已的折衷方式。Java 語(yǔ)言有許多有助于延遲綁定的功能,這些功能都以相對較高的復雜度為代價(jià)。反射、依賴(lài)性注入以及 XML 配置都可用于延遲綁定和減少耦合。一直以來(lái),Java 語(yǔ)言都是通過(guò)添加功能(如面向方面編程)來(lái)使其更為動(dòng)態(tài)的。您也許會(huì )認為 Java 開(kāi)發(fā)人員擁有了所需的一切。但還有一類(lèi)語(yǔ)言 —— 如 Smalltalk、Self 和 Ruby —— 要比 Java 還要動(dòng)態(tài)且允許用更佳的方式來(lái)延遲綁定。 這些語(yǔ)言可以提供 Java 語(yǔ)言所沒(méi)有的技術(shù),如覆蓋在方法丟失時(shí)發(fā)生的行為。請記住,Java 語(yǔ)言需要存在用于編譯時(shí)綁定的方法。其他語(yǔ)言允許打開(kāi)的類(lèi),這些類(lèi)能夠基于開(kāi)發(fā)人員需求進(jìn)行改變。如果您曾長(cháng)久關(guān)注框架的發(fā)展,就會(huì )發(fā)現對延遲綁定的需求在日益增長(cháng),這種需求導致了 Java 語(yǔ)言中出現了許多很不自然的捆綁,它們使這門(mén)語(yǔ)言變得復雜且模糊。而其他語(yǔ)言則持觀(guān)望態(tài)度,等著(zhù)我們去構建這類(lèi)框架來(lái)實(shí)現更高的抽象級別以及更高的效率。對于您來(lái)說(shuō),好處很明顯:可以獲得一門(mén)更易表達且更高效的語(yǔ)言。 為了理解連續區間中的點(diǎn),可以看一下反射的情況。使用 Java 語(yǔ)言,可以在運行時(shí)裝載類(lèi),通過(guò)反射找到一個(gè)方法,為該方法驗證正確的參數設置,然后執行該方法。要實(shí)現這些功能很可能需要編寫(xiě)很多代碼行。但為延遲綁定所做出的這些努力常常會(huì )得不償失,所以大多數 Java 應用程序開(kāi)發(fā)人員不會(huì )使用此項技術(shù)。Ruby、Smalltalk 和 Self 都使用一種原操作(如 Ruby 中的 對類(lèi)型策略和綁定策略越是深入研究就越會(huì )發(fā)現:等到運行時(shí)再綁定到調用或類(lèi)型會(huì )根本性地改變編程的過(guò)程,從而開(kāi)啟一個(gè)全新的可能世界。沒(méi)錯,您會(huì )發(fā)現這樣不那么安全。但您也會(huì )發(fā)現:重復少了、功能強大了并且在減少代碼行的同時(shí)有了更大的靈活性。為了理解這一切是如何運行的,下面將快速介紹一下 Smalltalk、Self 和 Ruby。首先介紹延遲綁定調用方法,然后介紹一些可以在運行時(shí)改變類(lèi)定義的可用技術(shù)。
延遲調用 在靜態(tài)語(yǔ)言中,編譯器在編譯時(shí)直接將調用綁定到實(shí)現。動(dòng)態(tài)語(yǔ)言則有些不同。Ruby、Smalltalk 和 Self 依賴(lài)于消息傳送來(lái)延遲綁定??蛻?hù)機使用消息傳送來(lái)指定目標對象、消息和參數集。這完全是一個(gè)運行機制。所以動(dòng)態(tài)語(yǔ)言有效地添加了一級間接尋址。它們將消息名綁定到一個(gè)對象上,而不是從調用綁定到類(lèi)型再到實(shí)現。然后,將該對象綁定到一個(gè)名稱(chēng)或標記,并使用該名稱(chēng)或標記在運行時(shí)查找相關(guān)的實(shí)現。它是這樣工作的:
上述所有步驟都發(fā)生在運行時(shí) 。這意味著(zhù)在執行該消息的語(yǔ)句前,既不需要目標方法,也不需要實(shí)現。在 Smalltalk 中,一切皆是對象,且大多數行為都利用消息傳送。甚至于控制結構都依賴(lài)它。Smalltalk 有三種消息:一元消息(無(wú)參數)、二元消息(帶固定的參數集)和關(guān)鍵字消息(帶已命名的參數)。例如:
Ruby 支持消息傳送和直接方法調用。Ruby 中的消息傳送看起來(lái)有些許不同,但前提是一致的。在清單 2 中,定義了帶 清單 2. 在 Ruby 中用兩種方式調用方法
如果您是一名 Java 程序員,這些都不是什么新鮮內容。您當然可以通過(guò) Java 的反射 API 處理后期綁定。只是需要更多的努力來(lái)實(shí)現同樣的功能。但用任意字符串(可以通過(guò)編程進(jìn)行修改)調用方法確實(shí)打開(kāi)了語(yǔ)言中一項額外的功能。最為重要的是,能夠輕易地調用在運行時(shí)過(guò)后所添加的任意行為。 Self 語(yǔ)言將消息傳送這一概念發(fā)揮到極致。在 Self 中,一切皆是對象。通過(guò)消息傳送專(zhuān)門(mén)地調用所有 Self 行為。Self 不含類(lèi)(通過(guò)復制其他對象創(chuàng )建新對象)也不含變量(只有帶方法和對象的已命名的 slot)。Self 使用消息傳送來(lái)調用已命名的 slot 和方法。其他大多數面向對象的語(yǔ)言通過(guò)允許直接訪(fǎng)問(wèn)實(shí)例數據弱化了封裝的價(jià)值,但 Self 通過(guò)加強用于訪(fǎng)問(wèn)方法和實(shí)例數據的相同協(xié)議克服了這一不足。發(fā)送一條消息調用一個(gè) slot。如果一個(gè) slot 有一個(gè)對象,該對象返回其值。Self 對訪(fǎng)問(wèn)屬性和訪(fǎng)問(wèn)方法不作區別。這項簡(jiǎn)化措施讓 Self 成為了一門(mén)簡(jiǎn)單但功能卻很強大的編程語(yǔ)言。像 Smalltalk 一樣,Self 用消息傳送來(lái)表示控制結構,Self 依賴(lài)于一個(gè)一直運行的 image。Self 中的對象都有一個(gè)父對象以及包含其他對象或方法的 slot。從 Self 對消息傳送的嚴重依賴(lài)以及 Self 應用程序一直運行這一概念可以看出:延遲綁定是 Self 的中心課題。 Self 中的消息傳送和 Smalltalk 中的幾乎一樣。在 Smalltalk 中 當您將
在運行時(shí)添加行為 所有這三種語(yǔ)言(Self、Smalltalk 和 Ruby)都使在運行時(shí)添加行為變得十分簡(jiǎn)單。使用 Self 和 Smalltalk,對現有類(lèi)所做的任何更改都是通過(guò)定義一個(gè)運行時(shí)修改來(lái)實(shí)現的。當添加一個(gè)方法時(shí),也有效地修改了一個(gè)活動(dòng)的類(lèi)。在 Self 中添加或刪除 slot 很簡(jiǎn)單:只需要發(fā)送 Ruby 框架常使用在運行時(shí)通過(guò)幾種不同的機制修改類(lèi)的技術(shù)。最簡(jiǎn)單的是打開(kāi)類(lèi)??梢源蜷_(kāi)任何的 Ruby 類(lèi),并通過(guò)重命名、添加或刪除方法或屬性來(lái)更改它。假設您想要在 Ruby 中擴展數字來(lái)簡(jiǎn)化柱狀圖的實(shí)現過(guò)程。您可以打開(kāi) 清單 3. 擴展 Fixnum
這個(gè)例子展示了當您在運行程序時(shí)知道了該行為后該如何擴展 Ruby 類(lèi)。當想要基于未知規則給一個(gè)類(lèi)添加任意行為時(shí),則需要使用一項不同的技術(shù)??梢栽陬?lèi)的上下文中估計字符串。以清單 4 為例。清單 3 擴展了 清單 4. 可擴展的類(lèi)
清單 4 中的
現狀和超越 在清單 4 的例子中,從內部擴展了 反射的功能也不能不提。Self、Ruby 和 Smalltalk 通常都進(jìn)行反射。其消息傳送功能允許不迫使用戶(hù)訪(fǎng)問(wèn)物理的方法就調用方法,正如在 Java 語(yǔ)言中那樣。 到目前為止,我主要探討了關(guān)于綁定到方法的內容,但延遲綁定的內容要多得多??匆幌虑鍐?5 中所示的 Ruby 的方法定義。 清單 5. 添加兩個(gè)數字
如果采用類(lèi)似的 Java 方法,就需要鍵入參數。這個(gè) Ruby 方法返回兩個(gè)數字的和。使用該方法惟一的要求是:第一個(gè)對象實(shí)現 類(lèi)似的 Java 方法只適用于單一類(lèi)型的參數。而這個(gè) Ruby 方法能夠服務(wù)于浮點(diǎn)型、整型、字符串型以及任何支持
用 Java 語(yǔ)言延遲綁定 Java 社區對靜態(tài)類(lèi)型檢查的迷戀程度令人驚訝,Java 程序員們正在不遺余力地尋找延遲綁定的方式。有些方法是成功的。諸如 Spring 等框架的存在主要是為了延遲綁定,它有助于減緩客戶(hù)機和服務(wù)之間的耦合。面向方面的編程通過(guò)提供能夠擴展類(lèi)的功能(甚至可以超出其當前的功能)的服務(wù)來(lái)實(shí)現延遲綁定。像 Hibernate 這樣的框架也可以延遲綁定,通過(guò)反射、代理和其他工具在運行時(shí)將持久性功能添加到純粹、普通的 Java 對象(POJO)中?,F在有很多關(guān)于如何用 POJO 編程的流行書(shū)籍可供開(kāi)發(fā)人員參考,這些書(shū)籍大多會(huì )使用愈加復雜的技術(shù)(比反射還要先進(jìn)),而這些技術(shù)主要是為了打開(kāi)類(lèi)并延遲綁定,從而有效地回避了靜態(tài)類(lèi)型。 在其他地方,延遲綁定的方法就不那么成功。依賴(lài)于 XML 來(lái)延遲綁定的部署描述符有很多問(wèn)題。對 XML 的過(guò)分依賴(lài)和我們對語(yǔ)言中的動(dòng)態(tài)行為的強烈渴望有很大關(guān)系,因為這些語(yǔ)言常常有點(diǎn)太過(guò)靜態(tài),綁定得有點(diǎn)太早,并且有點(diǎn)太受限制。 現在已經(jīng)有一些語(yǔ)言和技術(shù)可以為 Java 程序員們極想解決的這幾類(lèi)問(wèn)題提供解決方案,例如透明的持久性、為可測試性減少耦合、更加豐富的插件模型等。只要看看推動(dòng) Java 持久性框架發(fā)展的元程序設計,并同 Active Record、Gemstone 或 Og(動(dòng)態(tài)語(yǔ)言中的持久性框架)中類(lèi)似的解決方案對比一下,一切就一目了然了(參見(jiàn) 參考資料)?,F在延遲綁定變得越來(lái)越重要,并且推動(dòng)該過(guò)程的那些思想和做法在其它語(yǔ)言中也甚為高效。當您需要進(jìn)行元程序設計時(shí),請打開(kāi)您的工具箱,加入幾種允許延遲綁定的語(yǔ)言。不要害怕跨越邊界! 參考資料 學(xué)習
獲得產(chǎn)品和技術(shù)
討論
|
聯(lián)系客服