2016-04-19 6 views
3

In Ruby können Sie Getter/Setter-Methoden erstellen, indem Sie der Klassendefinition Folgendes hinzufügen."attr_reader" für alle Instanzvariablen deklarieren, ohne sie aufzulisten

attr_reader :key1, :key2, :key4 
attr_writer :key1, :key2, :key3 

oder äquivalent

attr_accessor :key1, :key2, :key3 

Zum Beispiel

class Foo 
    attr_reader :blah, :bar, :foo, :rah, :jar 

    def initialize a, b, c 
     @blah = calculate_blah(a,b) 
     @bar = calculate_bar(a) 
     @foo = calculate_foo(b,c) 
     @rah = calculate_rah(a,b,c) 
     @jar = calculate_jar(a,c) 
    end 
    # etc etc 
end 

Lasst uns sagen, es gibt viele Instanzvariablen, und ich möchte Getter-Methoden auf allen von ihnen.

Ist es möglich, attr_reader für alle Instanzvariablen zu deklarieren, ohne sie alle aufzulisten?

Der Vorteil ist, dass Sie nicht zwei Listen von Variablen (die identisch sind) eine in der initialize Methode, eine andere mit attr_reader pflegen müssen. Wenn Sie später eine weitere Instanzvariable hinzufügen möchten, müssen Sie sie später nur noch in der initialize-Methode einrichten, ohne sie ebenfalls der Liste attr_reader hinzufügen zu müssen.

Wenn dies nicht ohne weiteres mit attr_reader verfügbar ist, könnte vielleicht eine Metaprogrammierung herbeigerufen werden, um zu helfen?

Das Hauptziel hier ist zu verstehen, ob dies in Ruby möglich ist. Die Verwendung von Metaprogrammierung verursacht oft Kosten für Leistung und Unklarheit. Aber das geht über den Rahmen dieser Frage hinaus.

Ich bin mehr daran interessiert zu wissen, ob es möglich ist, etwas zu tun, nicht, ob das das Richtige ist.

+1

Sie ein Array dieser Instanzvariablen Die Namen bekam? –

+1

Instanzvariablen sind vor der Laufzeit nicht vorhanden. Sie werden lebendig, wenn ihnen ein Wert zugewiesen wird. Dies bedeutet, dass verschiedene Instanzen derselben Klasse abhängig von der in ihnen ausgeführten Logik verschiedene Instanzen von Instanzvariablen haben können. Jetzt wo Sie das wissen, an welchem ​​Punkt im Lebenszyklus des Objekts möchten Sie die Methoden erstellen? –

+2

"Vielleicht könnte eine metaprogrammierende Magie helfen" - Ihr genauer Anwendungsfall ist sehr unklar, aber wenn es aus reiner Faulheit heraus ist, dann sollten Sie keine Metaprogrammierung einbeziehen. Eine Klasse mit 100 Ivars ist schlecht genug :) –

Antwort

1

Auch wenn, wie andere gesagt haben, es keine gute Idee ist, ähnlichen Code zu verwenden, ist es möglich, die Meta-Funktion zu schreiben, die Sie verlangen. Dies ist eine von vielen möglichen unterschiedlichen Lösung. Die Idee ist, dass Sie jedes Objekt, das Sie einmal in seinem Lebenszyklus verwenden, erweitern, wenn die Instanzvariable bereits für das Objekt definiert ist. Dies kann während oder nach der Initialisierung geschehen.

class Foo 
    def initialize 
    @a = @b = @c = 33 
    # you could call define_attr_readers here 
    end 

    def define_attr_readers 
    # get the eigenclass of the current object 
    klass = class << self; self; end 

    symbols = instance_variables.map { |s| 
     # remove the @ at the start of the symbol 
     s.to_s[1..-1].to_sym 
    } 

    # augment the eigenclass 
    klass.class_eval do 
     symbols.each do |s| 
     attr_reader s 
     end 
    end 
    end 
end 

f = Foo.new 
f.define_attr_readers 
p f.a, f.b, f.c 
+1

Der offizielle Begriff für die" Eigenklasse "ist jetzt" Singleton Klasse ". Es gibt sogar einen Getter dafür, 'singleton_class'. –

+0

Danke für die Lösung. Wir sehen die Macht von Rubys Metaprogrammierung. –

3

Hier ist eine Lösung mit Singleton-Methode. Hinweis create_getters ist eine private Methode, so dass die Außenwelt die Verwendung von Metaprogrammierung (Implementierungsdetail) nicht kennt.

class Foo 
    def initialize a, b 
    @foo = a + b 
    @bar = a - b 
    @jar = a + b + 1000 

    create_getters 
    end 

    private 

    def create_getters 
    instance_variables.each do |v| 
     define_singleton_method(v.to_s.tr('@','')) do 
     instance_variable_get(v) 
     end 
    end 
    end 
end 

läuft dies in irb:

2.2.1 :082 > x=Foo.new 100, 99 
=> #<Foo:0x007fb4f3c31ce8 @foo=199, @bar=1, @jar=1199> 
2.2.1 :083 > x.foo 
=> 199 
2.2.1 :084 > x.bar 
=> 1 
2.2.1 :085 > x.jar 
=> 1199 

gewarnt: indem sie es auf diese Weise tun, Objektinstanziierung und Getter Methodenaufruf sind beide langsamer.

0

die Klasse instanziiert einmal die Instanzvariablen zu bewerten und die Klasse

class Hey 
    def initialize 
    @foo, @bar, @shabaz = [1,2,3] 
    end 
end 

Hey.new.instance_variables.tap do |inst_vars| 
    Hey.class_eval do 
    attr_reader *inst_vars.map { |var| var.to_s.sub(/^@/, '').to_sym } 
    end 
end 

p Hey.new.foo 
p Hey.new.bar 
p Hey.new.shabaz 
0

Schauen Sie, ma neu definieren! Keine Metaprogrammierung!

Angenommen

class A 
    def initialize 
    @b=0 
    @c=1 
    end 
end 

Dann

a = A.new 

a.instance_variables.each { |iv| self.class.send(:attr_reader, iv.to_s[1..-1].to_sym) } 

a.b #=> 0 
a.C#=> 1 

Wenn alle Instanzvariablen in initialize definiert sind, könnten Sie schreiben

class A 
    def initialize 
    @b=0 
    @c=1 
    end.tap { |instance| instance.instance_variables.each { |iv| 
    self.class.send(:attr_reader, iv.to_s[1..-1].to_sym) } } 
end 

a = A.new 
a.b #=> 0 
a.C#=> 1 
Verwandte Themen