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 gibt nicht mehr 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, Klasseninstanzvariablen werden nicht entlang der Vererbungskette weitergegeben!
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 für eine Klasse (nicht für eine Instanz dieser Klasse) können Sie etwas Gemeinsames für diese Klasse speichern, ohne dass Unterklassen diese automatisch abrufen (und umgekehrt). Mit Klassenvariablen müssen Sie nicht self.class
Aus einem Instanzobjekt schreiben, und (falls gewünscht) erhalten Sie auch eine automatische Freigabe in der gesamten Klassenhierarchie.
Kombinieren Sie diese 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]
Ich glaube, der Hauptunterschied 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" (d. H. Unterklassen) gemeinsam genutzt, wohingegen Klasseninstanzvariablen nur für diese Klasse spezifisch sind. Aber wenn Sie nie vorhaben, Ihre Klasse zu erweitern, ist der Unterschied rein akademisch.
#class-Instanzvariablen sind nur für Klassenmethoden und nicht für Instanzmethoden verfügbar, während Klassenvariablen sowohl für Instanzmethoden als auch für Klassenmethoden verfügbar sind. Auch die Klasseninstanzvariablen gehen in der Vererbungskette verloren, Klassenvariablen hingegen 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
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 existiert dieses Verhalten? Nun, alles in Ruby ist ein Objekt - sogar Klassen. Das heißt, jeder Klasse ist ein Objekt der Klasse Class
(bzw. einer Unterklasse von Class
) zugeordnet. (Wenn Sie class Foo
sagen, deklarieren Sie wirklich eine Konstante Foo
und weisen ihr ein Klassenobjekt zu.) Und jedes Ruby Objekt kann Instanzvariablen haben, also Klassenobjekte kann auch Instanzvariablen haben.
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 verfügt über ein eigenes Klassenobjekt, und dieses Klassenobjekt verfügt über eigene Instanzvariablen. Deshalb 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.
Die Verwendung von Klasseninstanzvariablen mag sofort nützlich erscheinen, da Klasseninstanzvariablen von Unterklassen gemeinsam genutzt werden und sowohl in Singleton- als auch in Instanzmethoden auf sie verwiesen werden kann. Es gibt jedoch einen erheblichen Nachteil. Sie werden gemeinsam genutzt, sodass Unterklassen den Wert der Klasseninstanzvariablen ändern können, und die Basisklasse wird auch von der Änderung betroffen sein, die normalerweise unerwünschtes Verhalten 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 class_attribute-Wert 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 ist jedoch, 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"
Official 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 betrachtet werden, mit all den Problemen, 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 Vorfahrenklasse wird später möglicherweise mit überraschenden Effekten wieder geöffnet und geändert:
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 dieses Verhalten explizit benötigen, sollten Sie besser Klasseninstanzvariablen verwenden.