1. 打開類
可以重新打開已經(jīng)存在的類(即使你不是這個(gè)類的創(chuàng)建者)并對(duì)之進(jìn)行動(dòng)態(tài)修改或者擴(kuò)展功能,即使像Array和String這樣標(biāo)準(zhǔn)庫的類也不例外,這種行為稱之為打開類
假設(shè)你想創(chuàng)建一個(gè)方法,功能是接受一個(gè)字符串參數(shù),只保留字符串中數(shù)字和字母,代碼如下
class String
def to_alphanumeric
#\w匹配單詞,等同于 /[A-Za-z0-9_]/
#\s匹配空格,等同于 /[ \t\r\n\f]/
gsub(/[^\w\s]/, '')
end
end
puts "A&^ar$o%n&* (is&*&))) t&*(*he B0&*S**^S)".to_alphanumeric
# => "Aaron is the B0SS"
2.猴子補(bǔ)丁
如果你粗心地為某個(gè)類添加了新功能,同時(shí)覆蓋了類原來的方法,進(jìn)而影響到其他部分的代碼,這種修改類的處理方法稱之為猴子補(bǔ)丁(Monkeypatch)
我們寫一個(gè)方法Array#replace(使用新字符替換數(shù)組中某個(gè)字符),但是Array類已經(jīng)有replace方法(功能是用一個(gè)作為參數(shù)傳入的數(shù)組替換當(dāng)前數(shù)組的全部?jī)?nèi)容),這樣就會(huì)把原有的方法覆蓋掉,不太好,可能我們?cè)緵]打算覆蓋原有的功能,代碼如下
class Array
def replace(original, replacement)
self.map {|e| e == original ? replacement : e }
end
end
puts ['x', 'y', 'z'].replace('x', 'a')
# => a, y, z
3.對(duì)象模型

這看起來像一個(gè)復(fù)雜的圖表,但是它清晰的表示出了 Ruby 中對(duì)象,類,模塊之間的聯(lián)系。有三個(gè)要點(diǎn)。
- 實(shí)例化的對(duì)象 (obj1,obj2,obj3) 是 MyClass 類的對(duì)象。
- MyClass 是屬于 Class 的對(duì)象(這在 Ruby 里也是對(duì)象的意思,我知道,這讓你很費(fèi)解)。
- MyClass 是 Class 的類對(duì)象,同時(shí)它也繼承自 Object。
我們還會(huì)在第二部分引用這個(gè),現(xiàn)在我們繼續(xù)看繼承鏈。
繼承鏈

當(dāng)你調(diào)用一個(gè)方法時(shí),Ruby 向右進(jìn)入的接收者類,然后向上遍歷繼承鏈,直到找到被調(diào)用方法或者抵達(dá)盡頭
在上圖b是對(duì)象Book類的一個(gè)實(shí)例對(duì)象,Book類包含兩個(gè)模塊Printable 和 Document.同時(shí)Book類繼承自O(shè)bject類(ruby中幾乎一切事物的基類),Object類又包含Kernel模塊,同時(shí)繼承自BasicObject類(ruby中一切對(duì)象的絕對(duì)父類)
prepend、include與祖先鏈
祖先鏈用于描述Ruby對(duì)象的繼承關(guān)系,因?yàn)轭惻c模塊是父子關(guān)系,所以祖先鏈中也可以包含模塊,prepend與include分別可以向鏈中添加模塊,不同的是調(diào)用include方法,模塊會(huì)被插入祖先鏈,當(dāng)前類的正上方,而prepend同樣是插入到祖先鏈,但位置卻在當(dāng)前類的正下方,另外通過Class.ancestors可以查看當(dāng)前的祖先鏈
4. 動(dòng)態(tài)定義方法(Module#define_method)
通過define_method方法取代了關(guān)鍵詞def,其本質(zhì)上都是相同的,只是在定義方式上,define_method的方式更加靈活一些,可以通過在編碼中通過推導(dǎo),完成函數(shù)的定義,增加了實(shí)現(xiàn)的靈活性。
示例代碼如下:
class MyClass
define_method :my_method do |my_arg|
my_arg * 3
do
end
obj = MyClass.new
obj.my_method(2) #=> 6
ActivateRecord(rails中的默認(rèn)ORM工具)中大量的使用了這種特性,例如
class Book < ActiveRecord::Base
end
b = Book.new
b.title
假定 Book 類是一個(gè) Book 數(shù)據(jù)庫表的一個(gè) ORM 包裝器,并且 title 就是這個(gè)表里的字段,那么我們就能得到由 b 表示的那個(gè)數(shù)據(jù)庫里的 title 所指定的那一列。
原理是:即使我們?cè)贐ook類中并沒有定義title屬性,當(dāng)這個(gè)類的實(shí)例對(duì)象調(diào)用這個(gè)方法時(shí)應(yīng)該報(bào)NoMethodError錯(cuò)誤,但是ActiveRecord會(huì)動(dòng)態(tài)添加這個(gè)方法,就和我們手動(dòng)添加的一樣
ActiveRecord源碼就是一個(gè)把元編程運(yùn)用到極致的例子
5. 動(dòng)態(tài)調(diào)用方法(Object#send)
在Ruby中通過Object#send方法可以代替點(diǎn)標(biāo)識(shí)調(diào)用對(duì)象的指定實(shí)例方法,好處是可以在編碼中動(dòng)態(tài)的決定方法的調(diào)用
由于在ruby中幾乎所有的類都繼承自O(shè)bject對(duì)象,所以可以在任意對(duì)象上調(diào)用send方法來訪問這個(gè)對(duì)象上的所有方法和屬性.Object也允許你調(diào)用私有屬性,但如果你的本意不是這樣就需要注意,可以使用Object#public_send這種方式,作用與直接訪問相同只是限制不能訪問私有方法或私有成員
代碼示例如下:
class MyClass
def my_method(my_arg)
my_arg * 2
end
end
obj = MyClass.new
obj.my_method(3) #=> 6
obj.send(:my_method, 3) #=> 6
%w(test1 test2 test3 test4 test5).each do |s|
# define_method動(dòng)態(tài)定義方法
define_method(s) do
puts "#{s} was called"
end
end
# send動(dòng)態(tài)調(diào)用方法
(1..5).each { |n| send("test#{n}") }
# => test1 was called
# => test2 was called
# => test3 was called
# => test4 was called
# => test5 was called
6. 動(dòng)態(tài)刪除方法(undef,undef_method和remove_method)
首先undef是關(guān)鍵字,remove_method和undef_method是方法
undef關(guān)鍵字和undef_method方法的作用一樣
remove_method 移除當(dāng)前類中的方法定義,當(dāng)前類仍然能使用從父類繼承過來的同名方法
undef_method 移除當(dāng)前類中的方法定義,并阻止使用從父類繼承過來的同名方法
2者對(duì)非當(dāng)前類定義的方法沒有影響,特別父類中定義的同名方法
class C
def say_hello
puts "hello C!"
end
end
class C1 < C
def say_hello
puts "hello C1!"
end
end
C.new.say_hello #hello C!
C1.new.say_hello #hello C1!
class C1 < C
remove_method :say_hello
end
C.new.say_hello #hello C!
C1.new.say_hello #hello C! 注意是C不是C1
class C1 < C
undef_method :say_hello
end
C.new.say_hello #hello C!
C1.new.say_hello #undefined method say_hello!
7. 魔術(shù)方法(BasicObject#method_missing)
可以創(chuàng)建一個(gè)在無此方法錯(cuò)誤事件(但在之前是會(huì)有錯(cuò)誤)前被自動(dòng)觸發(fā)調(diào)用的處理程序
method_missing利用的機(jī)制是,當(dāng)一個(gè)對(duì)象進(jìn)行某個(gè)方法調(diào)用的時(shí)候,會(huì)到其對(duì)應(yīng)的類的實(shí)例方法中進(jìn)行查找,如果沒有找到,則順著祖先鏈向上查找,直到找到BasicObject類為止。如果都沒有則會(huì)最終調(diào)用一個(gè)BasicObject#method_missing拋出NoMethodError異常。
與method_missing類似,還有關(guān)于常量的const_missing方法,當(dāng)引用一個(gè)不存在的常量時(shí),Ruby會(huì)把這個(gè)常量名作為一個(gè)符號(hào)傳遞給const_missing方法。
當(dāng)我們需要定義很多相似的方法時(shí)候,可以通過重寫method_missing方法,對(duì)相似的方法進(jìn)行統(tǒng)一做出回應(yīng),這樣一來其行為就類似與調(diào)用定義過的方法一樣。
代碼示例如下:
class Roulette
def method_missing(name, *args)
person = name.to_s.capitalize
#如果不是這幾個(gè)方法,走BasicObject類定義的method_missing方法
super unless %w[Bob Frank Bill Honda Eric].include? person
number = 0
3.times do
number = rand(10) + 1
puts "#{number}..."
end
"#{person} got a #{number}"
end
end
number_of = Roulette.new
puts number_of.bob
puts number_of.kitty
7. 補(bǔ)充:
動(dòng)態(tài)方法與Method_missing的使用原則
當(dāng)可以使用動(dòng)態(tài)方法時(shí)候,盡量使用動(dòng)態(tài)方法。除非必須使用method_missing方法(方法特別多的情況),否則盡量少使用它。
類與模塊
在ruby中類與模塊的區(qū)分不是太明顯,完全可以將二者相互替代,之所以同時(shí)保留二者的原因是為了保持代碼的清晰性,讓代碼意圖更加明確。使用原則:
希望把自己代碼包含(include,prepend)到別的代碼中,應(yīng)該使用模塊
希望某段代碼被實(shí)例化或被繼承,應(yīng)該使用類
模塊機(jī)制可以用來實(shí)現(xiàn)類似其他語言中的命名空間概念
::符號(hào)
雙冒號(hào)是定義 namespace 用的,或者叫 scope
當(dāng)你使用 Foo::Bar 的時(shí)候,實(shí)際你是在找一個(gè)名字叫 Foo 的 namespace ,然后讓它返回它里面的 Bar 參數(shù) , 這個(gè) Bar 可以是個(gè)常量,可以是個(gè)類,可以是個(gè)方法 (后兩者在 Ruby 中可視為常量)
同理使用 FooBar::method1 的時(shí)候?qū)嶋H上是在要求返回 FooBar 這個(gè) namespace 中 method1 這個(gè)「常量」的值。
使用 FooBar.method1 的時(shí)候則是在調(diào)用這個(gè)方法,當(dāng)然返回結(jié)果是一樣的,這里 :: 和 . 確實(shí)是可以互換不影響結(jié)果。但 :: 只能用來找 class method , instance method 就只能用 .
另外 :: 還能用來找真正的常量,比方這樣
class Foo
Bar = "hello"
bar = "hello"
end
Foo::Bar # => "hello"
Foo::bar # => 出錯(cuò)
Foo.Bar # => 出錯(cuò)
Foo.bar # => 出錯(cuò)
另外 :: 在開始位置則表示回到 root namespace ,就是不管前面套了幾個(gè) Module ,都算你其實(shí)寫在最外層。