2008-10-09 8 views
63

Jako ćwiczenie programistyczne napisałem fragment kodu Ruby, który tworzy klasę, tworzy dwa obiekty z tej klasy, monkeypatches jeden obiekt i polega na metodzie method_missing do monkeypatch drugiej.Ruby: define_method vs. def

Oto umowa. To działa zgodnie z przeznaczeniem:

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 

Zauważysz, że mam parametr method_missing. Zrobiłem to, ponieważ miałem nadzieję użyć metody define_method do dynamicznego tworzenia brakujących metod o odpowiedniej nazwie. Jednak to nie działa. W rzeczywistości, nawet przy użyciu define_method z nazwą statycznej tak:

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

Koniec z następującym wynikiem:

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. 

Co sprawia, że ​​komunikat o błędzie bardziej zdumiewające jest to, że mam tylko jeden argument dla method_missing. ..

Odpowiedz

132

define_method to (prywatna) metoda obiektu klasa. Dzwonisz z instancji . Nie ma metody instancji o nazwie define_method, więc powraca ona do numeru method_missing, tym razem z :define_method (nazwa brakującej metody) i :screech (jedyny argument przekazany do define_method).

Spróbuj zamiast (aby zdefiniować nową metodę na wszystkich obiektach małpa):

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 

Albo to (aby określić go tylko na obiekcie jest wezwany, używając „eigenclass” obiektu):

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

To wspaniała odpowiedź, Avdi, i wyjaśnia kilka innych pytań, które miałem. Dziękuję Ci. – gauth

+12

Zasadniczo pokazuje to, dlaczego zawsze powinieneś: a) używać białej listy w 'method_missing', abyś * tylko * obsługiwał metody, którymi jesteś * faktycznie * zainteresowany, i b) przekazywał wszystko, co robisz * nie * chce obsłużyć "w górę łańcucha pokarmowego", używając "super". –

+1

@ JörgWMittag mogę mieć więcej słów na linii * b) przekazać wszystko, czego nie chcesz obsługiwać "w górę łańcucha pokarmowego", używając super. ? * –

4

self.class.define_method (: pisk) nie działa, ponieważ define_method jest metoda prywatna można zrobić

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 
4
def method_missing(m) 
    self.class.class_exec do 
     define_method(:screech) {puts "This is the new screech."} 
    end 
end 

Metoda skrzek będzie dostępna dla wszystkich obiektów Małpy.