何时使用`method_missing`

8
在下面的代码中,类Lion中没有定义方法roar,但仍然可以使用method_missing调用。
class Lion
  def method_missing(name, *args)
    puts "Lion will #{name}: #{args[0]}"
  end
end

lion = Lion.new
lion.roar("ROAR!!!") # => Lion will roar: ROAR!!!

在什么情况下以及如何使用method_missing?使用它是否安全?


幽灵方法通常是元编程工具,需要注意效率问题。幽灵方法速度较慢,请谨慎使用。 - halfelf
我认为你的意思是 class Lion 而不是 Class Lion - Eric Walker
@EricWalker:谢谢,打字错误。 - tokhi
@EricWalker -- 真的吗,伙计? - Dylan Richards
@DylanRichards 是的,使用小写的 class 是必要的。在 Ruby 中,class 就像 ifdef 一样是一个关键字,而 Class 则是一个类的名称。Class Lion 会导致 NameError: uninitialized constant Lion 的错误。 - Nnnes
5个回答

9

只要按照预期的方式使用它,不要过度使用,它就是完全安全的。毕竟,并非所有你能做的事情都值得做。

method_missing 的优点是你可以以独特的方式响应各种东西。

缺点是你不会宣传自己的能力。其他期望你 respond_to? 某些内容的对象将不会得到确认,并可能以你不打算的方式处理你的自定义对象。

对于构建特定领域语言和提供组件之间非常松散的粘合剂,这种方法是无价的。

一个很好的例子是 Ruby OpenStruct 类。


1
+1 提到 method_missing 的孪生兄弟 responds_to? - akuhn
8
实际上,method_missing的孪生兄弟是respond_to_missing?你应该一起覆盖它们两个。 - Jörg W Mittag
@JörgWMittag 从未听说过 respond_to_missing,那是一个宝石吗? - akuhn
1
@akuhn:它在stdlib中:Object#respond_to_missing? - Sergio Tulentsev
@SergioTulentsev 谢谢!必须是1.9方法,在我的OSX上安装的1.8.7中不存在。 - akuhn
显示剩余2条评论

8
摘要:何时使用?当它能让你的生活更轻松,而不会使他人的生活更加复杂时。

这里有一个我想到的例子,来自redis_failover gem。

# Dispatches redis operations to master/slaves.
def method_missing(method, *args, &block)
  if redis_operation?(method)
    dispatch(method, *args, &block)
  else
    super
  end
end

这里我们检查调用的方法是否实际上是Redis连接命令。如果是,我们将其委派给底层连接。如果不是,则继续传递给上级。

method_missing应用的另一个著名例子是ActiveRecord finder。

User.find_by_email_and_age('me@example.com', 20)

当然,并没有一个名为find_by_email_and_age的方法。相反,method_missing会解析名称并使用正确的参数调用find方法。

4
这是我最喜欢的内容之一。
class Hash
  def method_missing(sym,*args)
    fetch(sym){fetch(sym.to_s){super}}
  end
end

让我可以像访问属性一样访问哈希值。这在处理JSON数据时非常方便。例如,不必编写tweets.collect{|each|each['text']},只需编写tweets.collect(&:text),这样更短。或者,不必编写tweets.first['author'],只需编写tweets.first.author,这样更自然。实际上,它为您提供了类似JavaScript访问哈希值的方式。
注意,我随时可能会被“猴子补丁警察”逮捕...

1
你也可以编写一个发出过程的方法,比如 tweets.collect(&attr('text')),它将是:def attr(a); lambda { |e| e[a] }; end - tadman
你说得对,但是我就不能写tweets.first.text了(我已经更新了我的帖子)。 - akuhn
为什么不使用OpenStruct,而要自己编写呢?创建一个“裸”对象是很棘手的。如果你有像hash这样的键,它会与内部方法冲突。 - tadman
@tadman JSON.parse(file) 不会创建 OpenStruct 实例,而是 Hash 实例,因此我进行了猴子补丁。 - akuhn
1
十一年后:Ruby JSON API有object_class,所以你可以返回一个OpenStruct :Dhttps://rubyapi.org/3.1/o/json#module-JSON-label-Parsing+Options - d3vkit
1
十一年后:Ruby JSON API有object_class,所以你可以返回一个OpenStruct :Dhttps://rubyapi.org/3.1/o/json#module-JSON-label-Parsing+Options - undefined

4
首先,请遵循Sergio Tulentsev的总结。
除此之外,我认为查看示例是了解在method_missing中正确和错误情况的最佳方法;因此,这里是另一个简单的示例:
最近我在Null Object中使用了method_missing
- Null Object替换了Order模型。 - Order存储不同货币的不同价格。
没有method_missing,它看起来像这样:
class NullOrder
  def price_euro
    0.0
  end

  def price_usd
    0.0
  end

  # ...
  # repeat for all other currencies
end

使用 method_missing,我可以缩短它到:
class NullOrder
  def method_missing(m, *args, &block)  
    m.to_s =~ /price_/ ? 0.0 : super
  end
end

这样做的好处是,我不必(记得)在添加新的price_xxx属性到Order时更新NullOrder


3
我还发现了一篇(Paolo Perrotta)的博客文章,其中演示了何时使用method_missing:
class InformationDesk
  def emergency
    # Call emergency...
    "emergency() called"
  end

  def flights
    # Provide flight information...
    "flights() called"
  end

  # ...even more methods
end

检查服务是否在午餐时间被要求。

class DoNotDisturb
  def initialize
    @desk = InformationDesk.new
  end

  def method_missing(name, *args)
    unless name.to_s == "emergency"
      hour = Time.now.hour
      raise "Out for lunch" if hour >= 12 && hour < 14
    end

    @desk.send(name, *args)
  end
end

# At 12:30...
DoNotDisturb.new.emergency # => "emergency() called"
DoNotDisturb.new.flights # ~> -:37:in `method_missing': Out for lunch (RuntimeError)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接