Ruby: define_method与def的区别

66
作为一个编程练习,我编写了一个Ruby片段来创建一个类,实例化两个来自该类的对象,monkeypatch(猴子补丁)其中一个对象,并依赖于method_missing来monkeypatch另一个对象。
以下是关键点。这可以按预期工作:
class Monkey

  def chatter
    puts "I am a chattering monkey!"
  end

  def method_missing(m)
    puts "No #{m}, so I'll make one..."
    def screech
      puts "This is the new screech."
    end
  end
end

m1 = Monkey.new
m2 = Monkey.new

m1.chatter
m2.chatter

def m1.screech
  puts "Aaaaaargh!"
end

m1.screech
m2.screech
m2.screech
m1.screech
m2.screech

你会注意到我有一个method_missing参数。我这样做是因为我希望使用define_method动态创建缺失的方法并赋予适当的名称。但是它不起作用。事实上,即使像这样使用具有静态名称的define_method:

def method_missing(m)
  puts "No #{m}, so I'll make one..."
  define_method(:screech) do
    puts "This is the new screech."
  end
end

最终结果如下:

ArgumentError: wrong number of arguments (2 for 1)

method method_missing   in untitled document at line 9
method method_missing   in untitled document at line 9
at top level    in untitled document at line 26
Program exited.

错误提示更让人困惑的是,我只为method_missing提供了一个参数...

3个回答

139

define_methodClass对象的私有方法。你在一个实例中调用它。没有名为define_method的实例方法,因此它会递归到你的method_missing方法,这次带有:define_method(缺失方法的名称)和:screech(你传递给define_method的唯一参数)。

尝试这样做(在所有Monkey对象上定义新方法):

def method_missing(m)
    puts "No #{m}, so I'll make one..."
    self.class.send(:define_method, :screech) do
      puts "This is the new screech."
    end
end

或者这样(仅在调用它的对象上定义它,使用对象的 "eigenclass"):
def method_missing(m)
    puts "No #{m}, so I'll make one..."
    class << self
      define_method(:screech) do
        puts "This is the new screech."
      end
    end
end

1
这是一个很棒的回答,Avdi,并且解决了我之前还有其他问题。谢谢你。 - gauth
13
通常情况下,这表明为什么您应该始终在method_missing中使用白名单,以便仅处理您实际感兴趣的那些方法,并且将您想处理的所有内容使用super“上移”,向上转发。 - Jörg W Mittag
2
@JörgWMittag,能否再多解释一下“b)使用super将所有不想处理的内容“上推”到更高层级”的意思? - Arup Rakshit

4
def method_missing(m)
    self.class.class_exec do
       define_method(:screech) {puts "This is the new screech."}
    end 
end

screech方法将适用于所有Monkey对象。


4

self.class.define_method(:screech)无法工作,因为define_method是私有方法。你可以这样做:

class << self
    public :define_method
end
def method_missing(m)
puts "No #{m}, so I'll make one..."
Monkey.define_method(:screech) do
  puts "This is the new screech."
end

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