2012-12-12 11 views
8

In Ruby 1.9.3 muss ich einige Klasseninstanzen anlegen, die jeweils ähnliche Instanz- und Klassenmethoden haben, aber nur um einige feste Parameter variieren. Die Unterscheidung ihrer Klassenart ist ebenfalls wichtig, daher kann ich nicht einfach einzelne Instanzen derselben Klasse verwenden.Dynamische Klassenmethoden in Ruby definieren

Ein vereinfachtes Beispiel sieht so aus.

module Animal 
private 
    def self.make_animal(name, legs, noise) 
    klass = Class.new 
    klass.const_set(:NUM_LEGS, legs) 
    klass.class.send(:define_method, :scream) { noise.upcase + '!' } 
    Animal.const_set(name, klass) 
    end 
    make_animal :Tiger, 4, 'roar' 
    make_animal :Human, 2, 'derp' 
end 

Dies scheint gut zu funktionieren, außer, dass die Variablen in dem Block verwendet, die die „Schrei“ Methode dynamisch definiert werden zur Laufzeit der „Schrei“ Methode anstelle der Laufzeit des „make_animal“ Verfahrens gebunden.

Animal::Human::NUM_LEGS # => 2 -- ok 
Animal::Tiger::NUM_LEGS # => 4 -- ok 
Animal::Human.scream # => "DERP!" -- ok 
Animal::Tiger.scream # => "DERP!" -- fail! 

Wie kann ich den obigen Code ändern, so dass der Tiger "ROAR!" schreit?

[Hinweis] Ich muss wirklich die doof OO-Struktur in dem Beispiel aus Gründen, die zu beschreibend sind hier zu halten. Ich bin nur daran interessiert zu lernen, wie man Klassenmethoden dynamisch mit parametrisierten Methodenimplementierungen definiert.

Antwort

10

klass.class ist in beiden Fällen gleich (Class): alle Klassen Instanzen Class sind. Als Ergebnis definierst du den Schrei und definierst ihn dann neu.

Was oft als Klassenmethoden in Ruby angesehen werden, sind tatsächlich Singleton-Methoden (es gibt viele Dinge über Eigenklassen usw. zu lesen, wenn Sie interessiert sind).

Die

def some_object.foo 
end 

Baukonstruktion schafft Singleton-Methoden. Sehr oft wird dies innerhalb einer Klassendefinition sein, selbst verwenden, aber Sie können es auf etwas, zum Beispiel tun, wenn Sie

tun
x = 'dog' 
def x.bark 
    "Woof" 
end 

Dann wird x.bark Einschlag zurück, aber Rinde wird an keinem anderes definiert werden Zeichenfolge.

Hier muss Ihre Methode auf Ihre noise Variable verweisen, daher müssen Sie define_singleton_method verwenden, um Ihre Methode zu definieren.

Wenn Sie noch in Ruby 1.8 sind, können Sie nicht define_singleton_method verwenden - Sie müssen die Tatsache verwenden, dass Singleton-Methoden Methoden für die Eigenklasse sind.

klass = Class.new 
eigenclass = class << klass; self; end 
eigenclass.send(:define_method, :scream){noise} 

entspricht der Verwendung von define_singleton_method

+5

Ach ja, 'define_singleton_method'.Habe es vergessen :) –

+0

Ja, "define_singleton_method" scheint das zu sein, was mir gefehlt hat. – maerics

4

Das Problem mit Ihrem Code ist nicht falsch Bindemoment. Sie definieren die Methode auf Class#class. Und es ist, Überraschung-Überraschung, Class, die einzige. Sie überschreiben also die "Brüll" -Version mit der "Derp" -Version.

Stattdessen sollten Sie direkt Methoden für diese dynamischen Klassen definieren. Hier ist mein Take (es verwendet Instanzvar für die noise, hoffe, das ist kein Problem).

module Animal 
private 
    def self.make_animal(name, legs, noise) 
    klass = Class.new 
    klass.const_set(:NUM_LEGS, legs) 
    klass.instance_variable_set(:@noise, noise) 

    klass.instance_eval do |k| 
     def scream 
     @noise.upcase + '!' 
     end 
    end 

    Animal.const_set(name, klass) 
    end 

    make_animal :Tiger, 4, 'roar' 
    make_animal :Human, 2, 'derp' 
end 

Animal::Human::NUM_LEGS # => 2 
Animal::Tiger::NUM_LEGS # => 4 

Animal::Human.scream # => "DERP!" 
Animal::Tiger.scream # => "ROAR!" 
+0

Ach ja, guten Fang auf dem 'klass.class ...' bit, dumm von mir =) – maerics

Verwandte Themen