認識 6 個被誤解的 Ruby 特性

發表於2012-06-12

來源:IBM developerWorks

簡介: 假設您是一名 C++ 開發人員,您需要使用 Ruby 快速執行一些原型設計。當您拿起一本 Ruby 參考書籍(比如 Pickaxe)或瀏覽 Ruby 網站時,會看到一些熟悉的構造,比如類宣告、執行緒支援和異常處理。正當您認為自己瞭解 Ruby 的工作原理之時,您意識到了,您 Ruby 程式碼中的併發機制與 Boost 執行緒工作原理不一樣,catch 和 throw 也與它們看上去的大不相同,而且其他人在其 Ruby 指令碼中各處使用了名為 self 的關鍵詞。歡迎來到 Ruby 的世界中!

如果您是一名 C++ 程式設計師且需要在 Ruby 環境中工作,那麼您有一些功課要做。本文討論了 Ruby 新手可能會誤解的六個 Ruby 特性,特別是當他或她來自一個類似但又不太相同的環境,比如 C++

● Ruby 類層次結構

● Ruby 中的單例方法

● self 關鍵詞

● method_missing 方法

● 異常處理

● 執行緒

 

注意:本文中所有的程式碼均進行測試,且基於 Ruby 版本 1.8.7。

 

Ruby 中的類層次結構

Ruby 中的類層次結構會很棘手。建立一個 Cat 型別的類並開始探討其層次結構(參見 清單 1)。

清單 1. Ruby 中的隱式類層次結構

Ruby 中的所有物件(甚至使用者定義的物件)都是 Object 類的後代,這在清單 1 中清晰可見。這與 C++ 是鮮明的對比。這一點也不像普通資料型別,例如 C/C++ int 或 double。清單 2 顯示了整數 1 的類層次結構。

清單 2. 整數 1 的類層次結構

到目前為止一切順利。現在您知道了類本身是 Class 型別的物件。而 Class 最終派生自 Object,如 清單 3 中所示使用 Ruby 內建的 String 類。

清單 3. 類的類層次結構

Module 是 Class 的基類,但是使用它時有一點要注意,即您不能直接例項化使用者定義的 Module 物件。如果您不想深入 Ruby 內部,最好考慮與 C++ 名稱空間有類似特徵的 Module:您可以定義您自己的方法、常量、等等。您在 Class 中包含了一個 Module,以及 voilà,Module 的所有元素現在會魔法般地成為 Class 的元素。清單 4 提供了一個示例。

清單 4. Module 不能進行直接例項化,並且只能與類一同使用

下面再重申一下重點:當您使用 Ruby 編寫 c = Cat.new 時,c 是派生自 Object 的 Cat 型別的一個物件。Cat 類是 Class 型別的一個物件,Class 派生自 Module,而 Module 又派生自 Object。因此該物件及其型別都是有效的 Ruby 物件。

單例方法和可編輯類

現在,看一下單例方法。假設您想使用 C++ 建模類似於人類社會的東西。那麼您會如何做呢?定義一個名為 Human 的類,然後定義數百萬的 Human 物件?這更像是在建模一個呆板的社會;每個人必須具惟一的特徵。Ruby 的單例方法在這裡就派上了用場,如 清單 5 所示。

清單 5. Ruby 中的單例方法

Ruby 中的單例方法 是僅與特定物件關聯的方法,不能用於一般的類。它們的字首是物件名稱。在 清單 5 中,paint 方法特定於 y物件,而且僅限於 y 物件;z.paint 導致一個 “方法未定義” 錯誤。您可以呼叫 singleton_methods 來查明一個物件中的單例方法列表:

不過在 Ruby 中有另一種定義單例方法的方式。看看 清單 6 中的程式碼。

清單 6. 建立單例方法的另一種方式

 

 

 

清單 5 還開創了新的可能性,可以新增新方法到使用者定義的類和內建的 Ruby 現有類,比如 String。這在 C++ 中是不可能實現的,除非您能夠訪問您使用的類的原始碼。再次觀察 String 類(清單 7)。

清單 7. Ruby 允許您修改一個現有的類

 

 

清單 7 清楚地展示瞭如何編輯一個現有的 Ruby 類來新增您自行選擇的方法。這裡,我新增了 palindrome? 方法到 String 類。因此 Ruby 類在執行時是可編輯的(一個強大的屬性)。

現在您對 Ruby 的類層次結構和單例有了一定的認識,接下來我們來看 self。注意,在定義 palindrome? 方法時我使用了 self

發現 self

self 關鍵詞的最常見用法可能就是在 Ruby 類中宣告一個靜態方法,如 清單 8 所示。

清單 8. 使用 self 宣告類的靜態方法

從 清單 8 的輸出中可以看到(如 清單 9 所示),沒有物件您無法呼叫非靜態方法。該行為類似於 C++

清單 9. 在沒有物件的情況下呼叫非靜態方法時會出錯

在探討 self 更深奧的用途和含義之前,注意您也可以通過在方法名稱前面加上類名來在 Ruby 中定義一個靜態方法:

 


清單 10 提供了 self 的一個更有趣但不太容易找到的用法。

清單 10. 使用元類來宣告靜態方法

該段程式碼以一種稍微不同的方式將 test 定義為一個類靜態方法。要了解究竟發生了什麼,您需要看一下 class << self 語法的一些細節。class << self … end 建立一個元類。在方法查詢鏈中,在訪問物件的基類之前先搜尋該物件的元類。如果您在元類中定義一個方法,可以在類上呼叫該方法。這類似於 C++ 中靜態方法的概念。

可以訪問一個元類嗎?是的:只需從 class << self … end 內返回 self。注意,在一個 Ruby 類宣告中,您沒有義務僅給出方法定義。清單 11 顯示了元類。

清單 11. 理解元類

回到 清單 7 的程式碼,您會看到 palindrome 被定義為 self == self.reverse。在該上下文中,self 與 C++ 沒有什麼區別。C++和 Ruby 中的方法都需要一個操作物件,以修改或提取狀態資訊。self 是指這裡的這個物件。注意,可以通過附加 self 字首來選擇性地呼叫公共方法,指明方法付諸作用的物件,如 清單 12 所示。

清單 12. 使用 self 呼叫方法

在 Ruby 中您無法通過附加 self 關鍵詞字首來呼叫私有方法。對於一名 C++ 開發人員,這可能會有點混淆。清單 13 中的程式碼明確表示,self 不能用於私有方法:對私有方法的呼叫只能針對隱式物件。

清單 13. self 不能用於私有方法呼叫

由於 Ruby 中的一切都是物件,當在 irb 提示符上呼叫 self 時您會得到以下結果:

 


一啟動 irb,Ruby 直譯器就為您建立主物件。這一主物件在 Ruby 相關的文章中也被稱為頂層上下文

關於 self 就介紹這麼多了。下面我們接著來看動態方法和 method_missing 方法。

method_missing 揭祕

看一下 清單 14 中的 Ruby 程式碼。

清單 14. 執行中的 method_missing

顯然,如果 voodoo 是您喜歡的,那麼清單 14 會給您這個恩典。這裡發生什麼了呢?我們建立了一個 Test 型別的物件,然後呼叫了t.f,以 23 作為引數。但是 Test 沒有以 f 作為方法,您應當會得到一個 NoMethodError 或類似的錯誤訊息。Ruby 在這裡做了一件很棒的事情:您的方法呼叫被阻截並由 method_missing 處理。method_missing 的第一個引數是缺失的方法名,在本例中是f。第二個(也是最後一個)引數是 *args,該引數捕獲傳遞給 f 的引數。您可以在何處使用像這樣的引數呢?在眾多選項之中,您可以輕鬆地將方法呼叫轉發到一個包含的 Module 或一個元件物件,而不為頂級類中的每個呼叫顯式提供一個包裝應用程式程式設計介面。

在 清單 15 中檢視更多 voodoo。

清單 15. 使用 send 方法將引數傳遞給一個例程

在 清單 15 中,class Test 有一個名為 method1 的方法被定義。但是,這裡沒有直接呼叫方法,而是發出對 send 方法的呼叫。send 是 Object 類的一個公共方法,因此可用於 Test(記住,所有類都派生自 Object)。send 方法的第一個引數是表示方法名稱的一個符號和字串。send 方法可以做到哪些您通常無法做到的事情?您可以使用 send 方法訪問一個類的私有方法。當然,對於這是否是一個好特性仍然頗具爭議。看一下 清單 16 中的程式碼。

清單 16. 訪問類私有方法

Throw 和 catch 並非表面那樣

如果您像我一樣具有 C++ 工作背景,且試圖編寫異常安全程式碼,那麼在看到 Ruby 有 throw 和 catch 關鍵詞時會開始感到異常親切。遺憾的是,throw 和 catch 在 Ruby 中的含義完全不同。

Ruby 通常使用 begin…rescue 塊處理異常。清單 17 提供了一個示例。

清單 17. Ruby 中的異常處理

在 清單 17 中,如果在試圖開啟檔案時出錯(可能是缺少檔案或檔案許可權方面的問題),rescue 塊中的程式碼會執行。ensure 塊中的程式碼始終執行,不管是否有任何異常引發。注意,rescue 塊後面是否緊跟 ensure 塊是可選的。另外,如果必須顯式地丟擲一個異常,那麼語法是 raise <MyException>。如果您選擇擁有您自己的異常類,可能會希望從 Ruby 內建的 Exception 類派生出相同的類,以利用現有方法。

Ruby 中的 catch 和 throw 程式碼塊實際上不是異常處理:您可以使用 throw 修改程式流程。清單 18 顯示了一個使用 throw 和 catch的示例。

清單 18. Ruby 中的 Throw 和 catch

在 清單 18 中,當程式碼執行到 throw 語句時,執行會被中斷,直譯器開始尋找處理相應符號的一個 catch 塊。在 catch 塊結束的地方繼續執行。檢視 清單 19 中的 throw 和 catch 示例:注意,您可以輕鬆將 catch 和 throw 語句用於各個函式。

有些人甚至說,Ruby 中對 catch 和 throw 的支援將 C goto 行為帶到一個全新的高度。鑑於函式可以有多個巢狀層,而 catch 塊可能在每一級,goto 行為類比似乎有據可循。

 清單 19. Ruby 中的異常處理:巢狀的 catch 塊

Ruby 中的執行緒可以是綠色的

Ruby 版本 1.8.7 不支援真正的併發性。確實不支援。但是您會說,在 Ruby 中有 Thread 建構函式。您說的沒錯。不過這個Thread.new 不會在您每次呼叫同一方法時生成一個真實的作業系統執行緒。Ruby 支援的是綠色執行緒:Ruby 直譯器使用單一作業系統執行緒來處理來自多個應用程式級執行緒的工作負載。

當某個執行緒等待一些輸入/輸出發生時,這一 “綠色執行緒” 概念很有用,而且您可以輕鬆排程一個不同的 Ruby 執行緒來充分利用 CPU。但是這一建構函式無法使用現代的多核 CPU(維基百科提供了一段內容,很好地解釋了什麼是綠色執行緒。參見 參考資料 獲取連結)。

最後這一個示例(參見 清單 20)證明了這一點。

清單 20. Ruby 中的多個執行緒

假設您的 Linux® 或 UNIX® 機器上擁有 top 實用程式,在終端執行程式碼,獲取程式 ID,然後再執行 top –p <process id>top啟動後,按住 Shift-H 來列出執行中執行緒的數量。您應當只能看到一個執行緒,確認了這一點:Ruby 1.8.7 中的併發性不過是個神話。

總的看來,綠色執行緒沒有什麼壞處。它們在重負荷輸入/輸出密集型程式中仍然有用,更不用說該方法可能是作業系統間最可移植的一個了。

 

結束語

本文涵蓋了以下多個方面:

● Ruby 中類層次結構的概念

● 單例方法

● 解釋 self 關鍵詞和 method_missing 方法

● 異常

● 執行緒

儘管 Ruby 不乏特立獨行之處,但是使用它進行程式設計還是挺有趣的,而且其以最少的程式碼完成大量工作的能力還是很強大的。難怪 Twitter 這樣的大型應用程式會使用 Ruby 來駕馭其真正的潛力。祝您有個快樂的 Ruby 程式設計體驗!

 


相關文章