method_missing and respond_to?

作者:周星 发布:2017-10-07

        不知道大家是否已经了解了 send 和 define_method,我们这次来学习method_missingrespond_to?,并且学习如何通过它们来灵活定义方法。

当我们调用一个不存在的方法时,程序会报出类似如下的错误:

irb(main):055:0* 1.xxx
NoMethodError: undefined method `xxx' for 1:Fixnum
    from (irb):55
    from /Users/zhouxing/.rbenv/versions/1.9.3-p125/bin/irb:12:in `<main>'

        这里我们在 1 这个数字上调用了名为 xxx() 的方法,这里简要介绍一下 ruby 的方法查找,当我们调用 xxx() 方法时,ruby 会到 1 这个对象的类中查找它的实例方法,如果没有找到,ruby 会沿着祖先链向上搜寻进入 Object 类,并且最终来到 Kernal 模块。

        由于 ruby 在哪里都没找到 xxx() 方法,只好承认失败,并在 1 这个对象上调用 method_missing() 方法,method_missing 是 Kernel 的一个实例方法,根据上次的分享,我们可以用 send() 方法来手动调用一下:

1.send :method_missing, :xxx
=> NoMethodError: undefined method `xxx' for 1:Fixnum

这个 NoMethodError 异常正是 Kernel#method_missing() 抛出的,所有找不到的方法最后都会来到这里。

搞懂了 method_missing() 的工作原理之后,我们就可以通过复写 method_missing() 方法来截取“找不到”的方法,还有调用时传递的参数和块:

#!/usr/bin/ruby
#encoding: utf-8
class Person
  def method_missing(method, *args)
    puts "你调用的方法时 #{method}, 参数为(#{args.join(',')})"
    puts "你传递了一个块" if block_given?
  end
end

bob = Person.new
bob.get_photos(2)
bob.jobs do |job|
  'xxx'
end

        我们定义了 Person 类,并重写了 method_missing 方法,结果不贴出,大家自己尝试执行一下,所以我们可以得出一个结论: 通过复写 method_missing() 方法,你可以调用实际上并不存在的方法! 当需要定义很多相似的方法时,可以通过响应 method_missing() 方法来免去手动定义这些方法的操作。

        大家在做 rails 的数据库查询工作时,rails 根据 model 的属性提供了很多动态的 find 方法,例如 Person 类存在 age, name 等属性,我们可以通过 Person.find_by_name, Person.find_by_age 来查找,但是事实上 ActiveRecord 并未定义这些方法,它也是通过 method_missing() 来做到的。接下来我们再看 respond_to?()方法,这个方法以问号结尾,熟悉 ruby 的同学会知道这个方法会返回一个bool类型的结果,respond_to?() 方法用来检验一个对象是否可以响应一个方法,下面举一个method_missing()结合respond_to?()的例子:

想了半天这个例子不好举,要写很多代码和假定条件等,暂时略过

        最后博主想说自己是不赞同定义 method_missing() 的,能不用尽量不用,除非写一写工具、Gem、或者一次性的脚本,个人认为 method_missing() 的可读性较差,而且如果考虑不够周全,可能引发让人难以捉摸的BUG,而且扩展性等都值得商榷。

如果您对本文有什么意见或建议,请联系博主。

支付宝扫码赞助博主


评论(0)