理解Ruby2.0中方法是如何查找與執行
預先介紹Ruby2.0是一個好的機會去回顧如何精確的用Ruby去樹立方法調用。
理解查找方法對于掌握Ruby的層次類是很有必要的。我準備的這篇文章有很多的代碼例子;你需要用Ruby 1.9.2 或者 更新的版本去調試運行大部分的例子,這有一個預先準備好的,它只能夠運行在Ruby2.0.0
類層次結構
讓我們以基類的繼承這一經典的例子開始吧
- class Animal
- def initialize(name)
- @name = name
- end
- def info
- puts "I'm a #{self.class}."
- puts "My name is '#{@name}'."
- end
- end
- class Dog < Animal
- def info
- puts "I #{make_noise}."
- super
- end
- def make_noise
- 'bark "Woof woof"'
- end
- end
- lassie = Dog.new "Lassie"
- lassie.info
- # => I bark "Woof woof".
- # I'm a dog.
- # My name is 'Lassie'.
在這個例子中,狗這個類繼承于動物類. 我們把動物類稱之為狗的超級類:
- Dog.superclass # => Animal
記住這個方法 Dog#info 調用超級類,這個特殊的關鍵字執行了后面接著的這個層次的定義,像這個例子 Animal#info 這個類可以用繼承類的屬性
- Dog.ancestors # => [Dog, Animal, Object, Kernel, BasicObject]
繼承類不以Animal結尾時很令人感興趣的地方
- Animal.superclass # => Object
這個Animal類的申明相當于之前寫了一個Animal類的對象
這就是為什么動物藥擁有比make_noise更多的方法,尤其反思的方法像是 respond_to?方法等等:
- lassie.respond_to? :upcase # => false
- lassie.methods
- # => [:nil?, :===, :=~, :!~, :eql?, :hash, :<=>, :class, :singleton_class, ...]
因此怎么引出內核和基本對象呢?待會我將回談到內核,事實上并不會說太多的基本對象之外的東西只有有限數量的方法并且會以所有類的層次結構收尾。
- # BasicObject is the end of the line:
- Object.superclass # => BasicObject
- BasicObject.superclass # => nil
- # It has very few methods:
- Object.instance_methods.size # => 54
- BasicObject.instance_methods.size # => 8
類是形成一棵樹的根莖所在 |
Mixins
雖然Ruby只支持單根繼承(也就是說,一個類只有一個超類),但它支持 mixins. mixin是一組可以包含到其他類的方法。在Ruby,實現為Module類:
- module Mamal
- def info
- puts "I'm a mamal"
- super
- end
- end
- Mamal.class # => Module
要在我們的Dog類中加入這個功能,我們可以用include 或者 prepend. 它們會插入這個module在Dog類之前或者之后:
- class Dog
- prepend Mamal
- end
- lassie = Dog.new "Lassie"
- lassie.info
- # => I'm a mamal.
- # I bark "Woof woof".
- # I'm a dog.
- # My name is 'Lassie'.
- Dog.ancestors # => [Mamal, Dog, Animal, Object, ...]
如果這個module被include到Dog類,而不是prepend到Dog類,效果類似,但是輸出的順序不一樣,你能猜到結果和祖先(ancestors)輸出的循序嗎? 點擊這里看結果 .
你可以隨意包含任意多個 include 和 prepend 的module。module還可以include和 prepende 其他module。當你不確定module和類得繼承體系時,馬上調用ancestors搞清楚。
單件類
在Ruby里, 在module和類得體系中,只有一個額外的層。任意對象可以有一個特殊的類優先于任何事:單件類(singleton class)
下面有個例子:
- scooby = Dog.new "Scooby-Doo"
- class << scooby
- def make_noise
- 'howl "Scooby-Dooby-Doo!"'
- end
- end
- scooby.info
- # => I'm a mamal.
- # I howl "Scooby-Dooby-Doo!".
- # I'm a dog.
- # My name is 'Scooby-Doo'.
請注意復吠叫那部分(I bark "Woof woof".)被史酷比特殊的嚎叫()代替了I howl "Scooby-Dooby-Doo!".那這不會影響Dog類得其他實例。
"class << scooby"是重新打開一個對象的單件類的特殊記法。還有一個方法可以定義單件方法:
- # 和上面的例子等同:
- def scooby.make_noise
- 'howl "Scooby-Dooby-Doo!"'
- end
單件類是個真正的類,可以通過調用singleton_class來訪問:
- # 單件類有個特別的名字:
- scooby.singleton_class # => #<Class:#<Dog:0x00000100a0a8d8>>
- # 單件類是個真正的類:
- scooby.singleton_class.is_a?(Class) # => true
- # 我們可以得到它實例方法的列表:
- scooby.singleton_class.instance_methods(false) # => [:make_noise]
所有Ruby對象都可以有單件類,包括類它們自己,甚至單件類自己也可以有單件類。
這聽起來有些瘋狂...那不是需要無數個單件類啊?某種意義上講,是的,但是Ruby會在他們需要的時候再去創建。
雖然上個例子是使用Dog類的一個實例的單件類。單更見的用在類上。. 實際上, " 類方法 " 就是單件類得方法。例如,attr_accessor 是Module單件類的一個實例方法。 Ruby on Rails 中的 ActiveRecord::Base 類有很多這樣的方法例如 has_many, validates_presence_of, 等等。下面可以看到ActiveRecord::Base的單件類的方法個數:
- Module.singleton_class
- .private_instance_methods
- .include?(:attr_accessor) # => true
- require 'active_record'
- ActiveRecord::Base.singleton_method
- .instance_methods(false)
- .size # => 170
單件類的名字來源于,他們有且只能有一個實例:
- scooby2 = scooby.singleton_class.new
- # => TypeError: can't create instance of singleton class
因為同樣的原因,你不能直接繼承一個單件類:
- class Scoobies < scooby.singleton_class
- # ...
- end
- # => TypeError: can't make subclass of singleton class
另一方面,單件類有一個完整的類體系。
對于對象;來說,我們有:
- scooby.singleton_class.superclass == scooby.class == Dog
- # => true, as for most objects
對于類來說,Ruby會自動設置超類,所以穿越超類或者單點類的不同路徑都相等:
- Dog.singleton_class.superclass == Dog.superclass.singleton_class
- # => true, as for any Class
這意味著Dog繼承Animal的實例方法以及它的單件方法。
為了徹底搞暈大家,我***寫下個extend的注解。它可以看做在被擴展對象的單件類include一個module的簡寫3:
- obj.extend MyModule
- # is a shortcut for
- class << obj
- include MyModule
- end
Ruby's 單件類遵從 eigenclass model.
方法查找和方法缺失
差不多都講完了!
Ruby支持的豐富的被繼承鏈是所有方法查找的基礎。
當查找到***的超類(BasicObject), Ruby 提供一個額外的方法處理方法缺失。
- lassie.woof # => NoMethodError: undefined method
- # `woof' for #<Dog:0x00000100a197e8 @name="Lassie">
- class Animal
- def method_missing(method, *args)
- if make_noise.include? method.to_s
- puts make_noise
- else
- super
- end
- end
- end
- lassie.woof # => bark "Woof woof!"
- scooby.woof # => NoMethodError ...
- scooby.howl # => howl "Scooby-Dooby-Doo!"
在上面的例子,當方法名是動物弄出來的噪音的一部分,我們調用make_noise,否則我們調用超類。超類會繼續向祖先查找知道到達BasicObject#method_missing,然后扔出NoMethodError 異常。
#p#
總結
總結一下,在Ruby中,當調用receiver.message 將會發生:
·沿著 receiver.singleton_class' 祖先鏈發送消息。
·然后沿著同樣的祖先鏈發送method_missing(消息)
在其中,***個找到的方法執行,并返回結果。任何對父類的調用重新恢復查找過程,找第二次符合的方法。
一個Module mod的祖先鏈是:
·每一個被它prepend的module的祖先鏈 (后prepend的module先查找)
·mod 它自己
·每一個被include的module的祖先鏈 (后include的module先查找)
·mod是個類,那就接下來查找它的超類的祖先鏈。
我們可以把上面的過程寫成偽碼4:
- class Module
- def ancestors
- prepended_modules.flat_map(&:ancestors) +
- [self] +
- included_modules.flat_map(&:ancestors) +
- is_a?(Class) ? superclass.ancestors : []
- end
- end
寫出實際的查找過程更復雜,但看起來大致像:
- class Object
- def send(method, *args)
- singleton_class.ancestors.each do |mod|
- if mod.defines? method
- execute(method, for: self, arguments: args,
- if_super_called: resume_lookup_at(mod))
- end
- end
- send :method_missing, method, *args
- end
- def method_missing(method, *args)
- # This is the end of the line.
- # If we're here, either no method was defined anywhere in ancestors,
- # and no method called 'method_missing' was defined either except this one,
- # or some methods were found but called `super` when there was
- # no more methods to be found but this one.
- raise NoMethodError, "undefined method `#{method}' for #{self}"
- end
- end
技術注腳
1 mixin有一些限制:
·Ruby對于同樣的module出現多次的體系支持的不是很好, hierarchy.ancestors 只會列出一個module一次,即使它被包含在祖先的 不同的層次 . 我還是很希望未來這個問題會解決,尤其為了避免 讓人尷尬的bug
·包含子模塊在模塊 M 不會影響之前包含模塊M的類,但是會影響之后包含模塊M的類。 請看這里.
·而且,Ruby 不允許祖先鏈包含循環。
2 Ruby實際上禁止對下面少數幾個類得單件類的訪問:Fixnum,Symbol 和 (從Ruby 2.0開始)Bignum 和 Float:
- 42.singleton_class # => TypeError: can't define singleton
作為經驗,立即數不能有單件類. 因為(1 << 42).class 是Fixnum 如果平臺是64位,否則是Bignum,對于Bignum也同樣適用。同樣的Ruby2.0后對于Float也同樣適用,因為一些浮點數也可能是立即數.
唯一的例外是nil,true 和false,他們的單件類是他們自己的類:nil.singleton_class 是 NilClass.
3 更準確的說,extend和單件類包含 缺省有同樣的效果,單他們調用不同的回調:extended vs included,append_features vs extend_object. 如果這些回調定義的不同,效果也最終會不同。
4 注意,prepended_modules不存在. 同樣singleton_class.ancestors 也不包含單件類自己, 但這個情況 會在Ruby 2.1改變 .
原文鏈接:http://www.oschina.net/translate/understanding-method-lookup-in-ruby-20#fn_extend