Ruby面向?qū)ο缶幊讨蓄惻c方法的基礎(chǔ)學(xué)習(xí)
打開(kāi)類和猴子補(bǔ)丁
在Ruby中,類定義的方法和其他的語(yǔ)句沒(méi)有任何區(qū)別,都是一行一行的執(zhí)行下去的。如下例子:
class Example def method_1 puts "method 1" end end class Example def method_2 puts "method 2" end end
本例中,當(dāng)?shù)谝淮味xClass Example的時(shí)候,還沒(méi)有一個(gè)叫做Example的Class存在,因此,Ruby開(kāi)始定義這個(gè)類,當(dāng)后面在定義這個(gè)類時(shí),Ruby會(huì)發(fā)現(xiàn)該類已存在,并返回這個(gè)類,而不是定義一個(gè)新類。
因?yàn)檫@個(gè)特性,因此,Ruby天生具有打開(kāi)一個(gè)已經(jīng)存在的類,并動(dòng)態(tài)修改其內(nèi)容的能力,即使其是標(biāo)準(zhǔn)類庫(kù)的類也不例外。比方說(shuō),可以給SDK的String類添加一個(gè)去除String中的標(biāo)點(diǎn)符號(hào)和特殊字符的方法:to_alphanumeric
class String def to_alphanumeric gsub /[^\w\s]/, '' end end “H&&^^ello”.to_alphanumeric #==>Hello
,然后,所有的String對(duì)象都具備“to_alphanumeric”的能力了,這種技術(shù)一般簡(jiǎn)稱為打開(kāi)類技術(shù)。
上面描述的打開(kāi)類技術(shù)其實(shí)是隱含了一定的風(fēng)險(xiǎn)的,尤其是在大型系統(tǒng)中使用打開(kāi)類技術(shù)擴(kuò)展標(biāo)準(zhǔn)類庫(kù)時(shí),因?yàn)?,很多開(kāi)發(fā)人員都在擴(kuò)展類,當(dāng)多個(gè)擴(kuò)展方法的名字一樣時(shí), 后定義的總會(huì)覆蓋掉前面,從而導(dǎo)致整個(gè)系統(tǒng)的崩潰,業(yè)界把這種魯莽的修改類的方式簡(jiǎn)稱為猴子補(bǔ)?。∕onkey Patch)。因此在使用打開(kāi)類技術(shù)時(shí),一定要慎之又慎。
實(shí)例變量
在Ruby中,實(shí)例變量是存儲(chǔ)在對(duì)象中,但是,其于該對(duì)象的類沒(méi)有關(guān)系,當(dāng)給對(duì)象的實(shí)例變量賦值時(shí),該實(shí)例變量就生成了,說(shuō)白了,實(shí)例變量就像是一個(gè)掛載在對(duì)象上的HashMap,每個(gè)對(duì)象都可以用自己不同的HashMap, 如下例:
class Person def name @name = "xianlinbox" end end p = Person.new puts p.instance_variables #==>nil p.name puts p.instance_variables #==>@name
方法
作為一個(gè)對(duì)象,除了有實(shí)例變量(也可以稱之為屬性),還需要有方法。 但是在Ruby中,關(guān)于方法的定義并不在對(duì)象中,而是在對(duì)象自身的類中,這是因?yàn)椤肮蚕硗粋€(gè)類的對(duì)象也必須共享同樣的方法”。但是,不能說(shuō)Class有一個(gè)叫做“method”的方法,因?yàn)闊o(wú)法使用"Class.method"調(diào)用該方法,而要說(shuō)Class有一個(gè)實(shí)例方法“method”,這意味著必須創(chuàng)建該類的實(shí)例對(duì)象,通過(guò)實(shí)例對(duì)象調(diào)用該方法。
如果要定義類方法,那么在定義方法的時(shí)候,必須加類名前綴,如下:
class Person def Person.name @name = "xianlinbox" end end
類本身也是對(duì)象
在Ruby中Class本身也是一個(gè)對(duì)象,關(guān)于對(duì)象的所有規(guī)則都適用于Class.
puts "hello".class #=> String puts String.class #=> Class puts Class.class #=> Class puts Class.instance_methods(false) #=> [:superclass,:allocate,:new] puts Class.instance_variables #=> nil
類的繼承體系
puts String.superclass #=> Object puts Class.superclass #=> Module puts Module.superclass #=> Object puts Object.superclass #=> BasicObjec puts BasicObject.superclass #=> nil
BasicObject是繼承體系的根節(jié)點(diǎn)。
所有類都繼承自O(shè)bject。
Class是對(duì)Module的繼承增強(qiáng),增加了new()和allocate()方法以創(chuàng)建實(shí)例。
方法的查找與執(zhí)行
Ruby中對(duì)象的方法都定義在類中,當(dāng)對(duì)象想要執(zhí)行一個(gè)方法時(shí),首先需要找到該方法,而Ruby編譯器查找方法的方式就是,第一步在自己的類中找,沒(méi)有的話,就沿著該類的祖先鏈(ancestors)一直往上找。
String.ancestors # => [String, Comparable, Object, Kernel, BasicObject]
為什么這兒會(huì)出現(xiàn)Comparable和Kernal, 這是因?yàn)镸odule的機(jī)制,當(dāng)一個(gè)類include一個(gè)模塊時(shí),編譯會(huì)把該模塊放在最靠近該類的祖先鏈上, String類include了Comparable模塊,而Kernal則是被Object類include的。
方法執(zhí)行的時(shí)候需要一個(gè)接收者,方法就會(huì)在接收者對(duì)象中被執(zhí)行,該接收者就是所謂的self對(duì)象。一般情況下,self對(duì)象是由最后一個(gè)接收該方法的對(duì)象擔(dān)當(dāng),在類和模塊的定義中(并且在任何方法的定義外),self對(duì)象由類或模塊擔(dān)任。
動(dòng)態(tài)調(diào)用方法
通常方法的調(diào)用方式是“對(duì)象名.方法名”,在Ruby中有一個(gè)很酷的特性,可以通過(guò)send()方法,把想調(diào)用的方法名作為參數(shù),這樣就可以在代碼運(yùn)行時(shí),直到最后時(shí)刻才決定調(diào)用哪個(gè)方法,這種技術(shù)稱之為動(dòng)態(tài)派發(fā)(Dynamic Dispatch)。這個(gè)技術(shù)非常有用,比方說(shuō),當(dāng)在項(xiàng)目中有一個(gè)配置文件對(duì)象,會(huì)根據(jù)配置文件初始化,在使用過(guò)程中,不同用戶可能會(huì)設(shè)置不同的值。通常做法是,判斷屬性的鍵值是對(duì)應(yīng)到哪個(gè)屬性,然后,調(diào)用對(duì)應(yīng)的set方法,代碼如下:
config.name = v if isNameProperty?(k) config.password = v if isPasswordProperty?(k) config.port = v if isPortProperty?(k) ...
看著這么一堆的長(zhǎng)得像親兄弟似的代碼,不由得產(chǎn)生一種閹掉它們的沖動(dòng)。如果使用動(dòng)態(tài)調(diào)用方法的話,代碼可以簡(jiǎn)化如下:
load_config('config.properties').each do |k, v| config.send("#{k}=", v) end
根據(jù)獲取的每個(gè)鍵值,去調(diào)對(duì)應(yīng)屬性的set方法,代碼清爽很多,而且以后擴(kuò)展config對(duì)象不需要修改load方法。
動(dòng)態(tài)定義方法
除了動(dòng)態(tài)調(diào)用方法外,Ruby甚至支持動(dòng)態(tài)定義方法,通過(guò)使用Module#define_method()方法,提供一個(gè)方法名和一個(gè)充當(dāng)方法體的塊即可定義一個(gè)方法。例:
class MyClass define_method :doubleString do |args| args * 2 end end t = MyClass.new puts t.doubleString("he") # => hehe
有了這個(gè)黑魔法之后,以后,就可以多個(gè)相似方法中不同的部分抽出來(lái)作為參數(shù),然后,使用一個(gè)方法定義搞定所有的方法。
method_missing()方法
Ruby是動(dòng)態(tài)語(yǔ)言,編譯器并不會(huì)檢測(cè)方法調(diào)用時(shí)的行為,因此你可以調(diào)用一個(gè)不存在的方法。 在運(yùn)行時(shí),所有找不到對(duì)應(yīng)方法的調(diào)用都會(huì)調(diào)用一個(gè)method_missing()方法,該方法定義在Kernal模塊中,因此每個(gè)對(duì)象都繼承了該方法。在kernal中method_missing()方法,會(huì)拋出一個(gè)NoMethodError的異常,這就是編譯器所做的工作。
但是,我們可以覆寫(xiě)這個(gè)方法,讓它變得很有意思。比方說(shuō),創(chuàng)建一個(gè)Struct,當(dāng)你想要新的屬性時(shí),只需要給它賦個(gè)值就神奇的產(chǎn)生了。
class MyStruct def initialize @attributes = {} end def method_missing(name, *args) attribute = name.to_s if attribute =~ /=$/ @attributes[attribute.chop] = args[0] else @attributes[attribute] end end end s = MyStruct.new s.weibo = "@xianlinbox" puts s.weibo # => @xianlinbox
這種,從調(diào)用者角度看,跟普通方法沒(méi)什么區(qū)別,但是實(shí)際接收者卻并沒(méi)有相對(duì)應(yīng)的方法的方法,Ruby術(shù)語(yǔ)稱之為幽靈方法(Ghost Method)。對(duì)于幽靈方法,除了自定義該方法以外,還可以把該方法轉(zhuǎn)發(fā)給另外一個(gè)對(duì)象的方法,當(dāng)然,你可以在轉(zhuǎn)發(fā)前后包裝一些自己的邏輯,這種處理技術(shù)稱之為動(dòng)態(tài)代理(Dynamic Proxy),和前面提到的動(dòng)態(tài)調(diào)用異曲同工。
使用了method_missing處理幽靈方法后,所有不存在方法的調(diào)用都會(huì)到這里來(lái),這可能會(huì)導(dǎo)致一些錯(cuò)誤信息不直觀的問(wèn)題(你看不到NoSuchMethod這樣的提示了),因此, 在使用method_missing方案的時(shí)候,一定要限定其使用范圍,并且調(diào)用父類的super.method_missing方法,讓不屬于這個(gè)范圍的,該報(bào)什么錯(cuò)還是報(bào)什么錯(cuò)。
白板類與保留方法
每一個(gè)類都有從父類繼承下來(lái)的一堆方法,在使用動(dòng)態(tài)代理技術(shù)時(shí),如果一個(gè)幽靈方法和真實(shí)的方法(你沒(méi)有意料到的繼承來(lái)的方法)發(fā)生名字沖突時(shí),后者會(huì)獲勝,從而導(dǎo)致系統(tǒng)報(bào)錯(cuò)。因此在使用動(dòng)態(tài)代理技術(shù)時(shí), 可以在代理類中刪除絕大多數(shù)繼承來(lái)的方法,避免發(fā)生名字沖突,刪除了繼承方法的類,就是所謂的白板類(Blank Slate),刪除類方法的途徑有2個(gè),一個(gè)是調(diào)用Module#undef_method方法,一個(gè)是調(diào)用Module#remove_method方法。
在Ruby的Object類中,有一些方法是內(nèi)部使用的,如果對(duì)其重新定義,或刪除,可能導(dǎo)致Ruby莫名其妙的掛掉,為了防止這種事情的發(fā)生,Ruby在這些方法名前面用“__”打頭,這些方法稱之為保留方法,當(dāng)你試圖修改這些方法時(shí),Ruby會(huì)給出警告。
相關(guān)文章
Ruby中創(chuàng)建字符串的一些技巧小結(jié)
這篇文章主要介紹了Ruby中創(chuàng)建字符串的一些技巧小結(jié),本文用先講解技巧然后給出代碼示例的方式列出了多種Ruby創(chuàng)建字符串方法,需要的朋友可以參考下2015-01-01Ruby單元測(cè)試框架TestUnit的替代者M(jìn)iniTest介紹
這篇文章主要介紹了Ruby單元測(cè)試框架TestUnit的替代者M(jìn)iniTest介紹,本文先是對(duì)比了TestUnit、MiniTest的優(yōu)劣,然后給出了MiniTest的使用示例,需要的朋友可以參考下2015-03-03Ruby使用C++擴(kuò)展實(shí)例(含C++擴(kuò)展代碼示例)
這篇文章主要介紹了Ruby使用C++擴(kuò)展實(shí)例,含C++擴(kuò)展實(shí)現(xiàn)代碼,本文可作為Ruby中使用C++擴(kuò)展的入門(mén)教程,需要的朋友可以參考下2014-09-09Ruby 中關(guān)于日文轉(zhuǎn)UTF-8及半角全角轉(zhuǎn)換的技巧
Ruby 中關(guān)于日文轉(zhuǎn)UTF-8及半角全角轉(zhuǎn)換的技巧,實(shí)現(xiàn)代碼。2009-05-05