Ruby-Klasseninstanzvariable vs. Klassenvariable

179

Ich habe " Wann werden Ruby-Instanzvariablen gesetzt? " Gelesen, aber ich bin mir nicht sicher, wann ich Klasseninstanzvariablen verwenden soll.

Klassenvariablen werden von allen Objekten einer Klasse gemeinsam genutzt. Instanzvariablen gehören zu einem Objekt. Es bleibt nicht viel Platz, um Klasseninstanzvariablen zu verwenden, wenn wir Klassenvariablen haben.

Könnte jemand den Unterschied zwischen diesen beiden erklären und wann man sie benutzt?

Hier ist ein Codebeispiel:

class S
  @@k = 23
  @s = 15
  def self.s
    @s
  end
  def self.k
     @@k
  end

end
p S.s #15
p S.k #23

Ich verstehe jetzt, dass Klasseninstanzvariablen nicht entlang der Vererbungskette übergeben werden!

Elmor
quelle

Antworten:

276

Instanzvariable für eine Klasse:

class Parent
  @things = []
  def self.things
    @things
  end
  def things
    self.class.things
  end
end

class Child < Parent
  @things = []
end

Parent.things << :car
Child.things  << :doll
mom = Parent.new
dad = Parent.new

p Parent.things #=> [:car]
p Child.things  #=> [:doll]
p mom.things    #=> [:car]
p dad.things    #=> [:car]

Klassenvariable:

class Parent
  @@things = []
  def self.things
    @@things
  end
  def things
    @@things
  end
end

class Child < Parent
end

Parent.things << :car
Child.things  << :doll

p Parent.things #=> [:car,:doll]
p Child.things  #=> [:car,:doll]

mom = Parent.new
dad = Parent.new
son1 = Child.new
son2 = Child.new
daughter = Child.new

[ mom, dad, son1, son2, daughter ].each{ |person| p person.things }
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]

Mit einer Instanzvariablen in einer Klasse (nicht in einer Instanz dieser Klasse) können Sie etwas speichern, das dieser Klasse gemeinsam ist, ohne dass Unterklassen diese automatisch auch abrufen (und umgekehrt). Mit Klassenvariablen haben Sie die Möglichkeit, nicht self.classaus einem Instanzobjekt schreiben zu müssen , und (falls gewünscht) erhalten Sie auch eine automatische Freigabe in der gesamten Klassenhierarchie.


Zusammenführen dieser Elemente zu einem einzigen Beispiel, das auch Instanzvariablen für Instanzen abdeckt:

class Parent
  @@family_things = []    # Shared between class and subclasses
  @shared_things  = []    # Specific to this class

  def self.family_things
    @@family_things
  end
  def self.shared_things
    @shared_things
  end

  attr_accessor :my_things
  def initialize
    @my_things = []       # Just for me
  end
  def family_things
    self.class.family_things
  end
  def shared_things
    self.class.shared_things
  end
end

class Child < Parent
  @shared_things = []
end

Und dann in Aktion:

mama = Parent.new
papa = Parent.new
joey = Child.new
suzy = Child.new

Parent.family_things << :house
papa.family_things   << :vacuum
mama.shared_things   << :car
papa.shared_things   << :blender
papa.my_things       << :quadcopter
joey.my_things       << :bike
suzy.my_things       << :doll
joey.shared_things   << :puzzle
suzy.shared_things   << :blocks

p Parent.family_things #=> [:house, :vacuum]
p Child.family_things  #=> [:house, :vacuum]
p papa.family_things   #=> [:house, :vacuum]
p mama.family_things   #=> [:house, :vacuum]
p joey.family_things   #=> [:house, :vacuum]
p suzy.family_things   #=> [:house, :vacuum]

p Parent.shared_things #=> [:car, :blender]
p papa.shared_things   #=> [:car, :blender]
p mama.shared_things   #=> [:car, :blender]
p Child.shared_things  #=> [:puzzle, :blocks]  
p joey.shared_things   #=> [:puzzle, :blocks]
p suzy.shared_things   #=> [:puzzle, :blocks]

p papa.my_things       #=> [:quadcopter]
p mama.my_things       #=> []
p joey.my_things       #=> [:bike]
p suzy.my_things       #=> [:doll] 
Phrogz
quelle
@Phronz Was ist der Unterschied zwischen self.things und self.class.things, die Sie im Code erwähnt haben?
Cyborg
1
@cyborg hat self.thingsauf eine Methode thingsim aktuellen Bereich verwiesen (im Fall einer Instanz einer Klasse ist dies die Methode der Instanz), wobei self.class.thingsauf eine thingsMethode aus der Klasse des aktuellen Bereichs verwiesen wird (wiederum im Fall einer Instanz einer Klasse würde dies bedeuten die Klassenmethode).
Graffzon
Schöne Erklärung.
aliahme922
30

Ich glaube, der Hauptunterschied (nur?) Ist die Vererbung:

class T < S
end

p T.k
=> 23

S.k = 24
p T.k
=> 24

p T.s
=> nil

Klassenvariablen werden von allen "Klasseninstanzen" (dh Unterklassen) gemeinsam genutzt, während Klasseninstanzvariablen nur für diese Klasse spezifisch sind. Wenn Sie jedoch niemals beabsichtigen, Ihre Klasse zu erweitern, ist der Unterschied rein akademisch.

bioneuralnet
quelle
1
Das ist nicht der einzige Unterschied. Die "geteilte" vs "Instanz" geht weiter als nur die Vererbung. Wenn Sie Instanz-Getter setzen, erhalten Sie S.new.s => nilund S.new.k => 23.
Andre Figueiredo
27

Quelle

Verfügbarkeit für Instanzmethoden

  • Klasseninstanzvariablen sind nur für Klassenmethoden und nicht für Instanzmethoden verfügbar.
  • Klassenvariablen stehen sowohl Instanzmethoden als auch Klassenmethoden zur Verfügung.

Vererbbarkeit

  • Klasseninstanzvariablen gehen in der Vererbungskette verloren.
  • Klassenvariablen sind nicht.
class Vars

  @class_ins_var = "class instance variable value"  #class instance variable
  @@class_var = "class variable value" #class  variable

  def self.class_method
    puts @class_ins_var
    puts @@class_var
  end

  def instance_method
    puts @class_ins_var
    puts @@class_var
  end
end

Vars.class_method

puts "see the difference"

obj = Vars.new

obj.instance_method

class VarsChild < Vars


end

VarsChild.class_method
Sachin Saxena
quelle
15

Wie bereits erwähnt, werden Klassenvariablen von einer bestimmten Klasse und ihren Unterklassen gemeinsam genutzt. Klasseninstanzvariablen gehören zu genau einer Klasse. seine Unterklassen sind getrennt.

Warum gibt es dieses Verhalten? Nun, alles in Ruby ist ein Objekt - sogar Klassen. Das bedeutet, dass jeder Klasse ein Objekt der Klasse Class(oder vielmehr eine Unterklasse von Class) entspricht. (Wenn Sie sagen class Foo, deklarieren Sie wirklich eine KonstanteFoo und weisen ihr ein Klassenobjekt zu.) Und jedes Ruby-Objekt kann Instanzvariablen haben, sodass Klassenobjekte auch Instanzvariablen haben können.

Das Problem ist, dass sich Instanzvariablen für Klassenobjekte nicht so verhalten, wie Sie es normalerweise von Klassenvariablen erwarten. Normalerweise möchten Sie, dass eine in einer Oberklasse definierte Klassenvariable mit ihren Unterklassen geteilt wird, aber so funktionieren Instanzvariablen nicht - die Unterklasse hat ein eigenes Klassenobjekt, und dieses Klassenobjekt hat seine eigenen Instanzvariablen. Daher haben sie separate Klassenvariablen mit dem Verhalten eingeführt, das Sie eher wollen.

Mit anderen Worten, Klasseninstanzvariablen sind eine Art Unfall von Rubys Design. Sie sollten sie wahrscheinlich nicht verwenden, es sei denn, Sie wissen genau, dass sie das sind, wonach Sie suchen.

Brent Royal-Gordon
quelle
Klassenvariable ist also wie statische Variable in Java?
Kick Buttowski
3

Offizielle Ruby-FAQ: Was ist der Unterschied zwischen Klassenvariablen und Klasseninstanzvariablen?

Der Hauptunterschied ist das Verhalten in Bezug auf die Vererbung: Klassenvariablen werden von einer Klasse und allen ihren Unterklassen gemeinsam genutzt, während Klasseninstanzvariablen nur zu einer bestimmten Klasse gehören.

Klassenvariablen können in gewisser Weise als globale Variablen im Kontext einer Vererbungshierarchie mit allen Problemen angesehen werden, die mit globalen Variablen verbunden sind. Beispielsweise kann eine Klassenvariable (versehentlich) von einer ihrer Unterklassen neu zugewiesen werden, was sich auf alle anderen Klassen auswirkt:

class Woof

  @@sound = "woof"

  def self.sound
    @@sound
  end
end

Woof.sound  # => "woof"

class LoudWoof < Woof
  @@sound = "WOOF"
end

LoudWoof.sound  # => "WOOF"
Woof.sound      # => "WOOF" (!)

Oder eine Ahnenklasse wird später wieder geöffnet und geändert, mit möglicherweise überraschenden Auswirkungen:

class Foo

  @@var = "foo"

  def self.var
    @@var
  end
end

Foo.var  # => "foo" (as expected)

class Object
  @@var = "object"
end

Foo.var  # => "object" (!)

Wenn Sie also nicht genau wissen, was Sie tun, und diese Art von Verhalten explizit benötigen, sollten Sie besser Klasseninstanzvariablen verwenden.

notapatch
quelle
2

Für diejenigen mit einem C ++ - Hintergrund könnte ein Vergleich mit dem C ++ - Äquivalent von Interesse sein:

class S
{
private: // this is not quite true, in Ruby you can still access these
  static int    k = 23;
  int           s = 15;

public:
  int get_s() { return s; }
  static int get_k() { return k; }

};

std::cerr << S::k() << "\n";

S instance;
std::cerr << instance.s() << "\n";
std::cerr << instance.k() << "\n";

Wie wir sehen können, kist eine staticähnliche Variable. Das ist 100% wie eine globale Variable, mit der Ausnahme , dass es im Besitz von der Klasse ( scoped korrekt zu sein). Dies erleichtert das Vermeiden von Konflikten zwischen ähnlich benannten Variablen. Wie bei jeder globalen Variablen gibt es nur eine Instanz dieser Variablen, und das Ändern ist immer für alle sichtbar.

Andererseits sist ein objektspezifischer Wert. Jedes Objekt hat eine eigene Instanz des Werts. In C ++ müssen Sie eine Instanz erstellen, um auf diese Variable zugreifen zu können. In Ruby ist die Klassendefinition selbst eine Instanz der Klasse (in JavaScript wird dies als Prototyp bezeichnet), daher können Sie sohne zusätzliche Instanziierung von der Klasse aus darauf zugreifen . Die Klasseninstanz kann geändert werden, die Änderung von sist jedoch für jede Instanz (jedes Objekt vom Typ S) spezifisch . Wenn Sie also einen ändern, ändert sich der Wert in einem anderen nicht.

Alexis Wilke
quelle
1

Während es sofort nützlich erscheinen mag, Klasseninstanzvariablen zu verwenden, da Klasseninstanzvariablen von Unterklassen gemeinsam genutzt werden und sowohl in Singleton- als auch in Instanzmethoden verwendet werden können, gibt es einen wesentlichen Nachteil. Sie werden gemeinsam genutzt, sodass Unterklassen den Wert der Klasseninstanzvariablen ändern können. Die Basisklasse ist auch von der Änderung betroffen, die normalerweise unerwünscht ist:

class C
  @@c = 'c'
  def self.c_val
    @@c
  end
end

C.c_val
 => "c" 

class D < C
end

D.instance_eval do 
  def change_c_val
    @@c = 'd'
  end
end
 => :change_c_val 

D.change_c_val
(irb):12: warning: class variable access from toplevel
 => "d" 

C.c_val
 => "d" 

Rails führt eine praktische Methode namens class_attribute ein. Wie der Name schon sagt, deklariert es ein Attribut auf Klassenebene, dessen Wert von Unterklassen vererbt werden kann. Auf den Wert class_attribute kann sowohl in Singleton- als auch in Instanzmethoden zugegriffen werden, wie dies bei der Klasseninstanzvariablen der Fall ist. Der große Vorteil von class_attribute in Rails besteht jedoch darin, dass Unterklassen ihren eigenen Wert ändern können und sich nicht auf die übergeordnete Klasse auswirken.

class C
  class_attribute :c
  self.c = 'c'
end

 C.c
 => "c" 

class D < C
end

D.c = 'd'
 => "d" 

 C.c
 => "c" 
Donato
quelle
Guter Anruf, ich hatte das noch nie benutzt. Es scheint zu funktionieren, obwohl Sie sicherstellen müssen, dass self.jedes Mal, wenn Sie auf das Attribut zugreifen möchten, ein Präfix vorangestellt wird c, z self.c. Die Dokumente sagen, dass ein default:Parameter übergeben werden kann, class_attributeaber es scheint aufgrund des Punktes, mit dem ich gerade erwähnt habe, nicht zu funktionieren self.
Dex
Wenn Sie sagen "Es mag sofort nützlich erscheinen, Klasseninstanzvariablen zu verwenden", meinen Sie damit "Klassenvariablen", nicht "Klasseninstanzvariablen, oder?" (Siehe ruby-lang.org/en/documentation/faq/8/. )
Keith Bennett
Ja, diese Antwort verwechselt völlig "Klasseninstanzvariablen" und "Klassenvariablen", worum es bei der Frage geht.
Stevo