認識 6 個被誤解的 Ruby 特性
如果您是一名 C++
程序員且需要在 Ruby 環境中工作,那么您有一些功課要做。本文討論了 Ruby 新手可能會誤解的六個 Ruby 特性,特別是當他或她來自一個類似但又不太相同的環境,比如 C++
:
- Ruby 類層次結構
- Ruby 中的單例方法
self
關鍵詞method_missing
方法- 異常處理
- 線程
注意:本文中所有的代碼均進行測試,且基于 Ruby 版本 1.8.7。
Ruby 中的類層次結構
Ruby 中的類層次結構會很棘手。創建一個 Cat
類型的類并開始探討其層次結構(參見清單 1)。
清單 1. Ruby 中的隱式類層次結構
irb (main):092:0> class Cat irb (main):093:1> end => nil irb (main):087:0> c = Cat.new => #<Cat:0x2bacb68> irb (main):088:0> c.class => Cat irb (main):089:0> c.class.superclass => Object irb (main):090:0> c.class.superclass.superclass => nil irb (main):091:0> c.class.superclass.superclass.superclass NoMethodError: undefined method `superclass' for nil:NilClass from (irb):91 from :0
Ruby 中的所有對象(甚至用戶定義的對象)都是 Object
類的后代,這在清單 1 中清晰可見。這與 C++
是鮮明的對比。這一點也不像普通數據類型,例如 C/C++ int
或 double
。清單 2 顯示了整數 1 的類層次結構。
清單 2. 整數 1 的類層次結構
irb (main):100:0> 1.class => Fixnum irb (main):101:0> 1.class.superclass => Integer irb (main):102:0> 1.class.superclass.superclass => Numeric irb (main):103:0> 1.class.superclass.superclass.superclass => Object
到目前為止一切順利。現在您知道了類本身是 Class
類型的對象。而 Class
最終派生自 Object
,如清單 3 中所示使用 Ruby 內置的 String
類。
清單 3. 類的類層次結構
irb (main):100:0> String.class => Class irb (main):101:0> String.class.superclass => Module irb (main):102:0> String.class.superclass.superclass => Object
Module
是 Class
的基類,但是使用它時有一點要注意,即您不能直接實例化用戶定義的 Module
對象。如果您不想深入 Ruby 內部,最好考慮與 C++
命名空間有類似特征的 Module
:您可以定義您自己的方法、常量、等等。您在 Class
中包含了一個 Module
,以及 voilà,Module
的所有元素現在會魔法般地成為 Class
的元素。清單 4 提供了一個示例。
清單 4. Module 不能進行直接實例化,并且只能與類一同使用
irb (main):020:0> module MyModule irb (main):021:1> def hello irb (main):022:2> puts "Hello World" irb (main):023:2> end irb (main):024:1> end irb (main):025:0> test = MyModule.new NoMethodError: undefined method `new' for MyModule:Module from (irb):25 irb (main):026:0> class MyClass irb (main):027:1> include MyModule irb (main):028:1> end => MyClass irb (main):029:0> test = MyClass.new => #<MyClass:0x2c18bc8> irb (main):030:0> test.hello Hello World => nil
下面再重申一下重點:當您使用 Ruby 編寫 c = Cat.new
時,c
是派生自 Object
的 Cat
類型的一個對象。Cat
類是 Class
類型的一個對象,Class 派生自 Module
,而 Module 又派生自 Object
。因此該對象及其類型都是有效的 Ruby 對象。
單例方法和可編輯類
現在,看一下單例方法。假設您想使用 C++
建模類似于人類社會的東西。那么您會如何做呢?定義一個名為 Human
的類,然后定義數百萬的 Human
對象?這更像是在建模一個呆板的社會;每個人必須具惟一的特征。Ruby 的單例方法在這里就派上了用場,如清單 5 所示。
清單 5. Ruby 中的單例方法
irb (main):113:0> y = Human.new => #<Human:0x319b6f0> irb (main):114:0> def y.paint irb (main):115:1> puts "Can paint" irb (main):116:1> end => nil irb (main):117:0> y.paint Can paint => nil irb (main):118:0> z = Human.new => #<Human:0x3153fc0> irb (main):119:0> z.paint NoMethodError: undefined method `paint' for #<Human:0x3153fc0> from (irb):119
Ruby 中的單例方法 是僅與特定對象關聯的方法,不能用于一般的類。它們的前綴是對象名稱。在清單 5 中,paint
方法特定于 y
對象,而且僅限于 y
對象;z.paint
導致一個 “方法未定義” 錯誤。您可以調用 singleton_methods
來查明一個對象中的單例方法列表:
irb (main):120:0> y.singleton_methods => ["paint"]
不過在 Ruby 中有另一種定義單例方法的方式。看看清單 6 中的代碼。
清單 6. 創建單例方法的另一種方式
irb (main):113:0> y = Human.new => #<Human:0x319b6f0> irb (main):114:0> class << y irb (main):115:1> def sing irb (main):116:1> puts "Can sing" irb (main):117:1> end irb (main):118:1>end => nil irb (main):117:0> y.sing Can sing => nil
清單 5 還開創了新的可能性,可以添加新方法到用戶定義的類和內置的 Ruby 現有類,比如 String
。這在 C++
中是不可能實現的,除非您能夠訪問您使用的類的源代碼。再次觀察 String
類(清單 7)。
irb (main):035:0> y = String.new ("racecar") => "racecar" irb (main):036:0> y.methods.grep (/palindrome/) => [ ] irb (main):037:0> class String irb (main):038:1> def palindrome? irb (main):039:2> self == self.reverse irb (main):040:2> end irb (main):041:1> end irb (main):050:0> y.palindrome? => true
清單 7 清楚地展示了如何編輯一個現有的 Ruby 類來添加您自行選擇的方法。這里,我添加了 palindrome?
方法到 String
類。因此 Ruby 類在運行時是可編輯的(一個強大的屬性)。
現在您對 Ruby 的類層次結構和單例有了一定的認識,接下來我們來看 self
。注意,在定義 palindrome?
方法時我使用了 self
。
self
關鍵詞的最常見用法可能就是在 Ruby 類中聲明一個靜態方法,如清單 8 所示。
class SelfTest def self.test puts "Hello World with self!" end endclass SelfTest2 def test puts "This is not a class static method" end end SelfTest.test SelfTest2.test
從清單 8 的輸出中可以看到(如清單 9 所示),沒有對象您無法調用非靜態方法。該行為類似于 C++
。
irb (main):087:0> SelfTest.test Hello World with self! => nil irb (main):088:0> SelfTest2.test NoMethodError: undefined method 'test' for SelfTest2:Class from (irb):88
在探討 self
更深奧的用途和含義之前,注意您也可以通過在方法名稱前面加上類名來在 Ruby 中定義一個靜態方法:
class TestMe def TestMe.test puts "Yet another static member function" end end TestMe.test # works fine
清單 10 提供了 self
的一個更有趣但不太容易找到的用法。
class MyTest class << self def test puts "This is a class static method" end end end MyTest.test # works fine
該段代碼以一種稍微不同的方式將 test
定義為一個類靜態方法。要了解究竟發生了什么,您需要看一下 class << self
語法的一些細節。class << self … end
創建一個元類。在方法查找鏈中,在訪問對象的基類之前先搜索該對象的元類。如果您在元類中定義一個方法,可以在類上調用該方法。這類似于 C++
中靜態方法的概念。
可以訪問一個元類嗎?是的:只需從 class << self … end
內返回 self
。注意,在一個 Ruby 類聲明中,您沒有義務僅給出方法定義。清單 11 顯示了元類。
irb (main):198:0> class MyTest irb (main):199:1> end => nil irb (main):200:0> y = MyTest.new => #< MyTest:0x2d43fe0> irb (main):201:0> z = class MyTest irb (main):202:1> class << self irb (main):203:2> self irb (main):204:2> end irb (main):205:1> end => #<Class: MyTest > irb (main):206:0> z.class => Class irb (main):207:0> y.class => MyTest
回到清單 7 的代碼,您會看到 palindrome
被定義為 self == self.reverse
。在該上下文中,self
與 C++
沒有什么區別。C++
和 Ruby 中的方法都需要一個操作對象,以修改或提取狀態信息。self
是指這里的這個對象。注意,可以通過附加 self
前綴來選擇性地調用公共方法,指明方法付諸作用的對象,如清單 12 所示。
irb (main):094:0> class SelfTest3 irb (main):095:1> def foo irb (main):096:2> self.bar () irb (main):097:2> end irb (main):098:1> def bar irb (main):099:2> puts "Testing Self" irb (main):100:2> end irb (main):101:1> end => nil irb (main):102:0> test = SelfTest3.new => #<SelfTest3:0x2d15750> irb (main):103:0> test.foo Testing Self => nil
在 Ruby 中您無法通過附加 self
關鍵詞前綴來調用私有方法。對于一名 C++
開發人員,這可能會有點混淆。清單 13 中的代碼明確表示,self
不能用于私有方法:對私有方法的調用只能針對隱式對象。
irb (main):110:0> class SelfTest4 irb (main):111:1> def method1 irb (main):112:2> self.method2 irb (main):113:2> end irb (main):114:1> def method3 irb (main):115:2> method2 irb (main):116:2> end irb (main):117:1> private irb (main):118:1> def method2 irb (main):119:2> puts "Inside private method" irb (main):120:2> end irb (main):121:1> end => nil irb (main):122:0> y = SelfTest4.new => #<SelfTest4:0x2c13d80> irb (main):123:0> y.method1 NoMethodError: private method `method2' called for #<SelfTest4:0x2c13d80> from (irb):112:in `method1'irb (main):124:0> y.method3 Inside private method => nil
由于 Ruby 中的一切都是對象,當在 irb
提示符上調用 self
時您會得到以下結果:
irb (main):104:0> self
=> main
irb (main):105:0> self.class => Object
一啟動 irb
,Ruby 解釋器就為您創建主對象。這一主對象在 Ruby 相關的文章中也被稱為頂層上下文。
關于 self
就介紹這么多了。下面我們接著來看動態方法和 method_missing
方法。
看一下清單 14 中的 Ruby 代碼。
irb (main):135:0> class Test irb (main):136:1> def method_missing (method, *args) irb (main):137:2> puts "Method: #{method} Args: (#{args.join (', ')})" irb (main):138:2> end irb (main):139:1> end => nil irb (main):140:0> t = Test.new => #<Test:0x2c7b850> irb (main):141:0> t.f (23) Method: f Args: (23) => nil
顯然,如果 voodoo 是您喜歡的,那么清單 14 會給您這個恩典。這里發生什么了呢?我們創建了一個 Test
類型的對象,然后調用了 t.f
,以 23
作為參數。但是 Test
沒有以 f
作為方法,您應當會得到一個 NoMethodError
或類似的錯誤消息。Ruby 在這里做了一件很棒的事情:您的方法調用被阻截并由 method_missing
處理。method_missing
的第一個參數是缺失的方法名,在本例中是 f
。第二個(也是最后一個)參數是 *args
,該參數捕獲傳遞給 f
的參數。您可以在何處使用像這樣的參數呢?在眾多選項之中,您可以輕松地將方法調用轉發到一個包含的 Module
或一個組件對象,而不為頂級類中的每個調用顯式提供一個包裝應用程序編程接口。
在清單 15 中查看更多 voodoo。
irb (main):142:0> class Test irb (main):143:1> def method1(s, y) irb (main):144:2> puts "S: #{s} Y: #{y}" irb (main):145:2> end irb (main):146:1> end => nil irb (main):147:0>t = Test.new irb (main):148:0> t.send (:method1, 23, 12) S: 23 Y: 12 => nil
在清單 15class Test
有一個名為 method1
的方法被定義。但是,這里沒有直接調用方法,而是發出對 send
方法的調用。send
是 Object
類的一個公共方法,因此可用于 Test
(記住,所有類都派生自 Object
)。send
方法的第一個參數是表示方法名稱的一個符號和字符串。send
方法可以做到哪些您通常無法做到的事情?您可以使用 send
方法訪問一個類的私有方法。當然,對于這是否是一個好特性仍然頗具爭議。看一下清單 16 中的代碼。
清單 16. 訪問類私有方法
irb (main):258:0> class SendTest irb (main):259:1> private irb (main):260:1> def hello irb (main):261:2> puts "Saying Hello privately" irb (main):262:2> end irb (main):263:1> end => nil irb (main):264:0> y = SendTest.new => #< SendTest:0x2cc52c0> irb (main):265:0> y.hello NoMethodError: private method `hello' called for #< SendTest:0x2cc52c0> from (irb):265 irb (main):266:0> y.send (:hello) Saying Hello privately => nil
如果您像我一樣具有 C++
工作背景,且試圖編寫異常安全代碼,那么在看到 Ruby 有 throw
和 catch
關鍵詞時會開始感到異常親切。遺憾的是,throw
和 catch
在 Ruby 中的含義完全不同。
Ruby 通常使用 begin…rescue
塊處理異常。清單 17 提供了一個示例。
begin f = File.open ("ruby.txt") # .. continue file processing rescue ex => Exception # .. handle errors, if any ensure f.close unless f.nil? # always execute the code in ensure block end
在清單 17 中,如果在試圖打開文件時出錯(可能是缺少文件或文件權限方面的問題),rescue
塊中的代碼會運行。ensure
塊中的代碼始終運行,不管是否有任何異常引發。注意,rescue
塊后面是否緊跟 ensure
塊是可選的。另外,如果必須顯式地拋出一個異常,那么語法是 raise <MyException>
。如果您選擇擁有您自己的異常類,可能會希望從 Ruby 內置的 Exception
類派生出相同的類,以利用現有方法。
Ruby 中的 catch 和 throw 代碼塊實際上不是異常處理:您可以使用 throw
修改程序流程。清單 18 顯示了一個使用 throw
和 catch
的示例。
irb (main):185:0> catch :label do irb (main):186:1* puts "This will print" irb (main):187:1> throw :label irb (main):188:1> puts "This will not print" irb (main):189:1> end This will print => nil
在清單 18 中,當代碼運行到 throw
語句時,執行會被中斷,解釋器開始尋找處理相應符號的一個 catch
塊。在 catch
塊結束的地方繼續執行。查看清單 19 中的 throw
和 catch
示例:注意,您可以輕松將 catch
和 throw
語句用于各個函數。
清單 19. Ruby 中的異常處理:嵌套的 catch 塊
irb (main):190:0> catch :label do irb (main):191:1* catch :label1 do irb (main):192:2* puts "This will print" irb (main):193:2> throw :label irb (main):194:2> puts "This won't print" irb (main):195:2> end irb (main):196:1> puts "Neither will this print" irb (main):197:1> end This will print => nil
有些人甚至說,Ruby 中對 catch
和 throw
的支持將 C goto
行為帶到一個全新的高度。鑒于函數可以有多個嵌套層,而 catch
塊可能在每一級,goto
行為類比似乎有據可循。
Ruby 版本 1.8.7 不支持真正的并發性。確實不支持。但是您會說,在 Ruby 中有 Thread 構造函數。您說的沒錯。不過這個 Thread.new
不會在您每次調用同一方法時生成一個真實的操作系統線程。Ruby 支持的是綠色線程:Ruby 解釋器使用單一操作系統線程來處理來自多個應用程序級線程的工作負載。
當某個線程等待一些輸入/輸出發生時,這一 “綠色線程” 概念很有用,而且您可以輕松調度一個不同的 Ruby 線程來充分利用 CPU。但是這一構造函數無法使用現代的多核 CPU(維基百科提供了一段內容,很好地解釋了什么是綠色線程。參見 參考資料 獲取鏈接)。
最后這一個示例(參見清單 20)證明了這一點。
清單 20. Ruby 中的多個線程
#!/usr/bin/env ruby def func (id, count) i = 0; while (i < count) puts "Thread #{i} Time: #{Time.now}" sleep (1) i = i + 1 end end puts "Started at #{Time.now}" thread1 = Thread.new{func (1, 100)} thread2 = Thread.new{func (2, 100)} thread3 = Thread.new{func (3, 100)} thread4 = Thread.new{func (4, 100)} thread1.join thread2.join thread3.join thread4.join puts "Ending at #{Time.now}"
假設您的 Linux® 或 UNIX® 機器上擁有 top
實用程序,在終端運行代碼,獲取進程 ID,然后再運行 top –p <process id>
。top
啟動后,按住 Shift-H 來列出運行中線程的數量。您應當只能看到一個線程,確認了這一點:Ruby 1.8.7 中的并發性不過是個神話。
總的看來,綠色線程沒有什么壞處。它們在重負荷輸入/輸出密集型程序中仍然有用,更不用說該方法可能是操作系統間最可移植的一個了。
結束語
本文涵蓋了以下多個方面:
- Ruby 中類層次結構的概念
- 單例方法
- 解釋
self
關鍵詞和method_missing
方法 - 異常
- 線程
盡管 Ruby 不乏特立獨行之處,但是使用它進行編程還是挺有趣的,而且其以最少的代碼完成大量工作的能力還是很強大的。難怪 推ter 這樣的大型應用程序會使用 Ruby 來駕馭其真正的潛力。祝您有個快樂的 Ruby 編程體驗!
參考資料
學習
- 閱讀 Programming Ruby: The Pragmatic Programmers' Guide(Dave Thomas,Chad Fowler 和 Andy Hunt;第二版),這是一本 Ruby 必讀書籍,也就是廣為人知的 Pickaxe 圖書。
- 查閱另一個寶貴的 Ruby 資源 The Ruby Programming Language [Yukihiro "Matz" Matsumoto(Ruby 的創建者)和 David Flanagan,O'Reilly,2008 年]。
- 訪問 To Ruby From C and C++,這是一個面向希望學習 Ruby 的
C/C++
程序員的一個不錯站點。 - 在維基百科上了解更多有關 綠色線程 的解釋信息。
- IBM Rational 推ter。
- 觀看 演示如何用 WebSphere Studio 快速開發 Web Services,包括面向初學者的產品安裝和設置演示,以及為經驗豐富的開發人員提供的高級功能。
- 在 developerWorks Linux 專區 尋找為 Linux 開發人員(包括 Linux 新手入門)準備的更多參考資料,查閱我們 最受歡迎的文章和教程。
- 在 developerWorks 上查閱所有 Linux 技巧 和 Linux 教程。
- 隨時關注 developerWorks 技術活動和網絡廣播。
- 訪問 developerWorks Open source 專區獲得豐富的 how-to 信息、工具和項目更新以及最受歡迎的文章和教程,幫助您用開放源碼技術進行開發,并將它們與 IBM 產品結合使用。
獲得產品和技術
- 使用 IBM 產品評估試用版軟件(可以通過下載獲得),并使用專門面向開發人員的軟件改進您的下一個開源開發項目。
討論
- 加入 developerWorks 中文社區,developerWorks 社區是一個面向全球 IT 專業人員,可以提供博客、書簽、wiki、群組、聯系、共享和協作等社區功能的專業社交網絡社區。
- 加入 IBM 軟件下載與技術交流群組,參與在線交流。
關于作者
Arpan Sen 是致力于電子設計自動化行業的軟件開發首席工程師。他使用各種 UNIX 版本(包括 Solaris、SunOS、HP-UX 和 IRIX)以及 Linux 和 Microsoft Windows 已經多年。他熱衷于各種軟件性能優化技術、圖論和并行計算。Arpan 獲得了軟件系統碩士學位。
來自: www.ibm.com