Ruby中的钩子方法详解

2019-09-25 09:41:20于丽


class Person
  def method_missing(sym, *args)
     "#{sym} not defined on #{self}"
  end

  def name
    "My name is Person"
  end
end

p = Person.new

puts p.name     # => My name is Person
puts p.address  # => address not defined on #<Person:0x007fb2bb022fe0>

method_missing 接收两个参数:被调用的方法名和传递给该方法的参数。 首先Ruby会寻找我们试图调用的方法,如果方法没找到则会寻找 method_missing 方法。 现在我们重载了 Person 中的 method_missing,因此Ruby将会调用它而不是抛出异常。

让我们来看看 Rake 是如何使用 method_missing 的。

Rake中的 method_missing

Rake 是Ruby中使用最广泛的gem包之一。Rake 使用 method_missing 来提供访问传递给Rake任务的参数。 首先创建一个简单的rake任务:


task :hello do
  puts "Hello"
end

如果你通过调用 rake hello 来执行这个任务,你会看到输出 Hello。 让我们扩展这个rake任务,以便接收一个参数(一个人名)并向他打招呼:

task :hello, :name do |t, args|
  puts "Hello #{args.name}"
end

t 是任务名,args 保存了传递过来的参数。正如你所见,我们调用 args.name 来获取传递给 hello 任务的 name 参数。 运行该任务,并传递一个参数:


rake hello["Imran Latif"]
=> Hello Imran Latif

让我们来看看 Rake 是如何使用 method_missing 为我们提供了传递给任务的参数的。

在上面任务中的 args 对象是一个 Rake::TaskArguments 实例,它是在这里所定义。 这个类负责管理传递给Rake任务的参数。查看 Rake::TaskArguments 的代码,你会发现并没有定义相关的方法将参数传给任务。 那么 Rake 是如何将参数提供给任务的呢?答案是 Rake 是使用了 method_missing 巧妙地实现了这个功能。 看看第64行 method_missing 的定义:

def method_missing(sym, *args)
  lookup(sym.to_sym)
end

在这个类中定义 method_missing 是为了保证能够访问到那些未定义的方法,而不是由Ruby抛出异常。 在 method_missing 中它调用了 lookup 方法:

def lookup(name)
  if @hash.has_key?(name)
   @hash[name]
  elsif @parent
    @parent.lookup(name)
  end
end

method_missing 调用 lookup,并将方法名以 Symbol(符号) 的形式传递给它。 lookup 方法将会在 @hash 中进行查找,它是在 Rake::TaskArguments 的构造函数中创建的。 如果 @hash 中包含该参数则返回,如果在 @hash 中没有则 Rake 会尝试调用 @parent 的 lookup。 如果该参数没有找到,则什么都不返回。