J'ai lu " Quand les variables d'instance Ruby sont-elles définies? " mais je ne suis pas du tout avisé d'utiliser des variables d'instance de classe.
Les variables de classe sont partagées par tous les objets d'une classe, les variables d'instance appartiennent à un seul objet. Il ne reste pas beaucoup de place pour utiliser les variables d'instance de classe si nous avons des variables de classe.
Quelqu'un pourrait-il expliquer la différence entre ces deux et quand les utiliser?
Voici un exemple de code:
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
Je comprends maintenant que les variables d'instance de classe ne sont pas transmises le long de la chaîne d'héritage!
Variable d'instance sur une classe:
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]
Variable de classe:
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]
Avec une variable d'instance sur une classe (pas sur une instance de cette classe), vous pouvez stocker quelque chose de commun à cette classe sans que les sous-classes ne les obtiennent aussi automatiquement (et vice-versa). Avec les variables de classe, vous avez la possibilité de ne pas avoir à écrire self.class
à partir d'un objet d'instance et (lorsque cela est souhaitable), vous bénéficiez également d'un partage automatique dans toute la hiérarchie de la classe.
En les fusionnant dans un seul exemple qui couvre également les variables d'instance sur des instances:
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
Et puis en action:
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]
Je crois que la principale (seule?) Différence est l'héritage:
class T < S
end
p T.k
=> 23
S.k = 24
p T.k
=> 24
p T.s
=> nil
Les variables de classe sont partagées par toutes les "instances de classe" (c'est-à-dire les sous-classes), alors que les variables d'instance de classe sont spécifiques à cette classe uniquement. Mais si vous n'avez jamais l'intention d'étendre votre classe, la différence est purement académique.
Les variables d'instance #class sont disponibles uniquement pour la méthode de classe et non pour les méthodes d'instance, alors que les variables de classe sont disponibles pour les méthodes d'instance et les méthodes de classe. De plus, les variables d'instance de classe sont perdues dans la chaîne d'héritage, contrairement aux variables de classe.
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
Comme d'autres l'ont dit, les variables de classe sont partagées entre une classe donnée et ses sous-classes. Les variables d'instance de classe appartiennent à exactement une classe; ses sous-classes sont séparées.
Pourquoi ce comportement existe-t-il? Eh bien, tout dans Ruby est un objet, même des classes. Cela signifie que chaque classe a un objet de la classe Class
(ou plutôt une sous-classe de Class
) lui correspondant. (Lorsque vous dites class Foo
, vous déclarez réellement une constante Foo
et vous lui affectez un objet de classe.) Et chaque objet Ruby peut avoir des variables d'instance afin que les objets de classe puissent avoir des variables d'instance, aussi.
Le problème est que les variables d'instance sur les objets de classe ne se comportent pas vraiment comme vous le souhaitez habituellement. Vous voulez généralement qu'une variable de classe définie dans une superclasse soit partagée avec ses sous-classes, mais ce n'est pas ainsi que fonctionnent les variables d'instance: la sous-classe a son propre objet de classe et cet objet de classe a ses propres variables d'instance. Ils ont donc introduit des variables de classe distinctes avec le comportement que vous êtes le plus susceptible de vouloir.
En d'autres termes, les variables d'instance de classe sont une sorte d'accident de la conception de Ruby. Vous ne devriez probablement pas les utiliser à moins de savoir précisément ce que vous recherchez.
Bien qu'il puisse sembler immédiatement utile d'utiliser des variables d'instance de classe, étant donné que les variables d'instance de classe sont partagées entre les sous-classes et qu'elles peuvent être référées à la fois par les méthodes singleton et instance, il existe un inconvénient majeur. Elles sont partagées et les sous-classes peuvent donc modifier la valeur de la variable d'instance de la classe. La classe de base sera également affectée par le changement, qui constitue généralement un comportement indésirable:
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 introduit une méthode pratique appelée class_attribute. Comme son nom l'indique, il déclare un attribut de niveau classe dont la valeur peut être héritée par des sous-classes. La valeur class_attribute est accessible dans les méthodes singleton et instance, comme dans le cas de la variable d'instance de classe. Cependant, l’énorme avantage que présente class_attribute dans Rails est que les sous-classes peuvent modifier leur propre valeur et n’ont pas d’impact sur la classe parente.
class C
class_attribute :c
self.c = 'c'
end
C.c
=> "c"
class D < C
end
D.c = 'd'
=> "d"
C.c
=> "c"
La principale différence est le comportement concernant l'héritage: les variables de classe sont partagées entre une classe et toutes ses sous-classes, alors que les variables d'instance de classe n'appartiennent qu'à une seule classe.
D'une certaine manière, les variables de classe peuvent être considérées comme des variables globales dans le contexte d'une hiérarchie d'héritage, avec tous les problèmes liés aux variables globales. Par exemple, une variable de classe peut (accidentellement) être réaffectée par l'une de ses sous-classes, affectant toutes les autres classes:
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" (!)
Ou bien, une classe d'ancêtres peut être ultérieurement rouverte et modifiée, avec des effets probablement surprenants:
class Foo
@@var = "foo"
def self.var
@@var
end
end
Foo.var # => "foo" (as expected)
class Object
@@var = "object"
end
Foo.var # => "object" (!)
Donc, à moins que vous sachiez exactement ce que vous faites et avez explicitement besoin de ce type de comportement, vous feriez mieux d'utiliser des variables d'instance de classe.