预先介绍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