開啟類和猴子補丁
在Ruby中,類定義的方法和其他的語句沒有任何區別,都是一行一行的執行下去的。如下例子:
class Example def method_1 puts "method 1" end end class Example def method_2 puts "method 2" end end
本例中,當第一次定義Class Example的時候,還沒有一個叫做Example的Class存在,因此,Ruby開始定義這個類,當後面在定義這個類時,Ruby會發現該類已存在,並返回這個類,而不是定義一個新類。
因為這個特性,因此,Ruby天生具有開啟一個已經存在的類,並動態修改其內容的能力,即使其是標準類庫的類也不例外。比方說,可以給SDK的String類添加一個去除String中的標點符號和特殊字元的方法:to_alphanumeric
class String def to_alphanumeric gsub /[^\w\s]/, '' end end “H&&^^ello”.to_alphanumeric #==>Hello
,然後,所有的String對象都具備“to_alphanumeric”的能力了,這種技術一般簡稱為開啟類技術。
上面描述的開啟類技術其實是隱含了一定的風險的,尤其是在大型系統中使用開啟類技術擴充標準類庫時,因為,很多開發人員都在擴充類,當多個擴充方法的名字一樣時, 後定義的總會覆蓋掉前面,從而導致整個系統的崩潰,業界把這種魯莽的修改類的方式簡稱為猴子補丁(Monkey Patch)。因此在使用開啟類技術時,一定要慎之又慎。
執行個體變數
在Ruby中,執行個體變數是儲存在對象中,但是,其於該對象的類沒有關係,當給對象的執行個體變數賦值時,該執行個體變數就產生了,說白了,執行個體變數就像是一個掛載在對象上的HashMap,每個對象都可以用自己不同的HashMap, 如下例:
class Person def name @name = "xianlinbox" end end p = Person.new puts p.instance_variables #==>nil p.name puts p.instance_variables #==>@name
方法
作為一個對象,除了有執行個體變數(也可以稱之為屬性),還需要有方法。 但是在Ruby中,關於方法的定義並不在對象中,而是在對象自身的類中,這是因為“共用同一個類的對象也必須共用同樣的方法”。但是,不能說Class有一個叫做“method”的方法,因為無法使用"Class.method"調用該方法,而要說Class有一個執行個體方法“method”,這意味著必須建立該類的執行個體對象,通過執行個體對象調用該方法。
如果要定義類方法,那麼在定義方法的時候,必須加類名首碼,如下:
class Person def Person.name @name = "xianlinbox" end end
類本身也是對象
在Ruby中Class本身也是一個對象,關於對象的所有規則都適用於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是繼承體系的根節點。
所有類都繼承自Object。
Class是對Module的繼承增強,增加了new()和allocate()方法以建立執行個體。
方法的尋找與執行
Ruby中對象的方法都定義在類中,當對象想要執行一個方法時,首先需要找到該方法,而Ruby編譯器尋找方法的方式就是,第一步在自己的類中找,沒有的話,就沿著該類的祖先鏈(ancestors)一直往上找。
String.ancestors # => [String, Comparable, Object, Kernel, BasicObject]
為什麼這兒會出現Comparable和Kernal, 這是因為Module的機制,當一個類include一個模組時,編譯會把該模組放在最靠近該類的祖先鏈上, String類include了Comparable模組,而Kernal則是被Object類include的。
方法執行的時候需要一個接收者,方法就會在接收者對象中被執行,該接收者就是所謂的self對象。一般情況下,self對象是由最後一個接收該方法的對象擔當,在類和模組的定義中(並且在任何方法的定義外),self對象由類或模組擔任。
動態調用方法
通常方法的調用方式是“對象名.方法名”,在Ruby中有一個很酷的特性,可以通過send()方法,把想調用的方法名作為參數,這樣就可以在代碼運行時,直到最後時刻才決定調用哪個方法,這種技術稱之為動態派發(Dynamic Dispatch)。這個技術非常有用,比方說,當在項目中有一個設定檔對象,會根據設定檔初始化,在使用過程中,不同使用者可能會設定不同的值。通常做法是,判斷屬性的索引值是對應到哪個屬性,然後,調用對應的set方法,代碼如下:
config.name = v if isNameProperty?(k) config.password = v if isPasswordProperty?(k) config.port = v if isPortProperty?(k) ...
看著這麼一堆的長得像親兄弟似的代碼,不由得產生一種閹掉它們的衝動。如果使用動態調用方法的話,代碼可以簡化如下:
load_config('config.properties').each do |k, v| config.send("#{k}=", v) end
根據擷取的每個索引值,去調對應屬性的set方法,代碼清爽很多,而且以後擴充config對象不需要修改load方法。
動態定義方法
除了動態調用方法外,Ruby甚至支援動態定義方法,通過使用Module#define_method()方法,提供一個方法名和一個充當方法體的塊即可定義一個方法。例:
class MyClass define_method :doubleString do |args| args * 2 end end t = MyClass.new puts t.doubleString("he") # => hehe
有了這個黑魔法之後,以後,就可以多個相似方法中不同的部分抽出來作為參數,然後,使用一個方法定義搞定所有的方法。
method_missing()方法
Ruby是動態語言,編譯器並不會檢測方法調用時的行為,因此你可以調用一個不存在的方法。 在運行時,所有找不到對應方法的調用都會調用一個method_missing()方法,該方法定義在Kernal模組中,因此每個對象都繼承了該方法。在kernal中method_missing()方法,會拋出一個NoMethodError的異常,這就是編譯器所做的工作。
但是,我們可以覆寫這個方法,讓它變得很有意思。比方說,建立一個Struct,當你想要新的屬性時,只需要給它賦個值就神奇的產生了。
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
這種,從調用者角度看,跟普通方法沒什麼區別,但是實際接收者卻並沒有相對應的方法的方法,Ruby術語稱之為幽靈方法(Ghost Method)。對於幽靈方法,除了自訂該方法以外,還可以把該方法轉寄給另外一個對象的方法,當然,你可以在轉寄前後封裝一些自己的邏輯,這種處理技術稱之為動態代理(Dynamic Proxy),和前面提到的動態調用異曲同工。
使用了method_missing處理幽靈方法後,所有不存在方法的調用都會到這裡來,這可能會導致一些錯誤資訊不直觀的問題(你看不到NoSuchMethod這樣的提示了),因此, 在使用method_missing方案的時候,一定要限定其使用範圍,並且調用父類的super.method_missing方法,讓不屬於這個範圍的,該報什麼錯還是報什麼錯。
白板類與保留方法
每一個類都有從父類繼承下來的一堆方法,在使用動態代理技術時,如果一個幽靈方法和真實的方法(你沒有意料到的繼承來的方法)發生名字衝突時,後者會獲勝,從而導致系統報錯。因此在使用動態代理技術時, 可以在代理類中刪除絕大多數繼承來的方法,避免發生名字衝突,刪除了繼承方法的類,就是所謂的白板類(Blank Slate),刪除類方法的途徑有2個,一個是調用Module#undef_method方法,一個是調用Module#remove_method方法。
在Ruby的Object類中,有一些方法是內部使用的,如果對其重新定義,或刪除,可能導致Ruby莫名其妙的掛掉,為了防止這種事情的發生,Ruby在這些方法名前面用“__”打頭,這些方法稱之為保留方法,當你試圖修改這些方法時,Ruby會給出警告。