2009-01-27 8 views
29

Ist es in Ruby möglich, einen Verweis auf Methoden eines Objekt zu bekommen (ich möchte, wenn wissen, dass dies ohne Procs/lambda getan werden kann), beispielsweise den folgenden Code betrachten:Wie kann ich einen Verweis auf eine Methode erhalten?


class X 
    def initialize 
    @map = {} 
    setup_map 
    end 

    private 
    def setup_map 
    # @map["a"] = get reference to a method 
    # @map["b"] = get reference to b method 
    # @map["c"] = get referebce to c method 
    end 

    public 
    def call(a) 
    @map["a"](a) if a > 10 
    @map["b"](a) if a > 20 
    @map["c"](a) if a > 30 
    end 

    def a(arg) 
    puts "a was called with #{arg}" 
    end 

    def b(arg) 
    puts "b was called with #{arg}" 
    end 

    def c(arg) 
    puts "c was called with #{arg}" 
    end 
end 

Ist es möglich, so etwas zu tun? Ich möchte procs/lambdas vermeiden, weil ich in der Lage sein möchte, das Verhalten von A, B, C durch Unterklassen zu ändern.

Antwort

42

Sie wollen Object#method:

---------------------------------------------------------- Object#method 
    obj.method(sym) => method 
------------------------------------------------------------------------ 
    Looks up the named method as a receiver in obj, returning a Method 
    object (or raising NameError). The Method object acts as a closure 
    in obj's object instance, so instance variables and the value of 
    self remain available. 

     class Demo 
      def initialize(n) 
      @iv = n 
      end 
      def hello() 
      "Hello, @iv = #{@iv}" 
      end 
     end 

     k = Demo.new(99) 
     m = k.method(:hello) 
     m.call #=> "Hello, @iv = 99" 

     l = Demo.new('Fred') 
     m = l.method("hello") 
     m.call #=> "Hello, @iv = Fred" 

Jetzt Ihr Code wird:

private 
def setup_map 
    @map = { 
    'a' => method(:a), 
    'b' => method(:b), 
    'c' => method(:c) 
    } 
    # or, more succinctly 
    # @map = Hash.new { |_map,name| _map[name] = method(name.to_sym) } 
end 

public 
def call(arg) 
    @map["a"][arg] if arg > 10 
    @map["b"][arg] if arg > 20 
    @map["c"][arg] if arg > 30 
end 
+0

Großartig! Du bist der Mann :) – Geo

4

Sie können dies mit Lambda-Ausdrücke tun, während die Fähigkeit beibehalten Verhalten in Subklassen zu ändern:

class X 
    def initialize 
    @map = {} 
    setup_map 
    end 

    private 
    def setup_map 
    @map["a"] = lambda { |a| a(a) } 
    @map["b"] = lambda { |a| b(a) } 
    @map["c"] = lambda { |a| c(a) } 
    end 

    public 
    def call(a) 
    @map["a"].call(a) if a > 10 
    @map["b"].call(a) if a > 20 
    @map["c"].call(a) if a > 30 
    end 

    def a(arg) 
    puts "a was called with #{arg}" 
    end 

    def b(arg) 
    puts "b was called with #{arg}" 
    end 

    def c(arg) 
    puts "c was called with #{arg}" 
    end 
end 
+0

Das funktioniert! Aber gibt es keine Möglichkeit, einen tatsächlichen Bezug zu einer Methode zu bekommen? – Geo

+0

Das beste, was Sie tun können, ist instance.methods und das gibt ein Array von Strings. – Samuel

1

Ruby-Methoden sind nicht Objekte erster Klasse; Es implementiert OO mit Nachrichtenübergabe.

class X 
    def call(a) 
    self.send(:a, a) if a > 10 
    self.send(:b, a) if a > 20 
    self.send(:c, a) if a > 30 
    end 

    def a(arg) 
    puts "a was called with #{arg}" 
    end 

    def b(arg) 
    puts "b was called with #{arg}" 
    end 

    def c(arg) 
    puts "c was called with #{arg}" 
    end 
end 

Oder sie einfach direkt anrufen:

def call(a) 
    self.a(a) if a > 10 
    self.b(a) if a > 20 
    self.c(a) if a > 30 
end 
+0

Dies war ein einfaches Beispiel. Für die Klassen, die ich schreiben muss, werde ich ungefähr 20-30 Methoden haben, um zu überprüfen. Wenn Schecks nicht sehr stilvoll wären, oder? – Geo

+0

Ich nahm an, dass die if-Anweisungen Proxies für einen Switch oder Hash-Lookup waren. "send (: a, a)" sieht für mich einfacher aus als "@map [" a "] [arg]", umso mehr, wenn es viele davon gibt. – Ken

0

Sie einen Verweis auf die bekommen Methode durch object.method(:method_name).

ZB: Um eine Referenz auf system Methode zu bekommen.

m = self.method(:system) 
m.call('ls)