Variable d'instance de classe Ruby et variable de classe

179

J'ai lu " Quand les variables d'instance Ruby sont-elles définies? ", Mais je ne suis pas d'accord pour utiliser les 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 objet. Il ne reste plus beaucoup de place pour utiliser des 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, les variables d'instance de classe ne sont pas transmises le long de la chaîne d'héritage!

Elmor
la source

Réponses:

276

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 les obtiennent automatiquement (et vice-versa). Avec les variables de classe, vous avez la commodité de ne pas avoir à écrire à self.classpartir d'un objet d'instance et (lorsque cela est souhaitable) vous obtenez également un partage automatique dans toute la hiérarchie de classes.


Les fusionner en un seul exemple qui couvre également les variables d'instance sur les 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] 
Phrogz
la source
@Phronz Quelle est la différence entre self.things et self.class.things que vous avez mentionné dans le code?
cyborg
1
@cyborg a self.thingsréférencé une méthode thingsdans la portée actuelle (dans le cas d'une instance d'une classe, ce sera la méthode de l'instance), où self.class.thingsréférence une thingsméthode de la classe de la portée actuelle (encore une fois dans le cas d'une instance d'une classe, cela signifierait la méthode de classe).
graffzon
Belle explication.
aliahme922
30

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 cours, la différence est purement académique.

bioneuralnet
la source
1
Ce n'est pas la seule différence. L'instance "partagée" vs "instance" va plus loin que l'héritage. Si vous mettez des getters d'instance, vous obtiendrez S.new.s => nilet S.new.k => 23.
André Figueiredo
27

La source

Disponibilité aux méthodes d'instance

  • Les variables d'instance de classe ne sont disponibles que pour les méthodes de classe et non pour les méthodes d'instance.
  • Les variables de classe sont disponibles pour les méthodes d'instance et les méthodes de classe.

Héritabilité

  • Les variables d'instance de classe sont perdues dans la chaîne d'héritage.
  • Les variables de classe ne le sont pas.
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
la source
15

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) qui lui correspond. (Quand vous dites class Foo, vous déclarez vraiment une constante Fooet lui assignez un objet de classe.) Et chaque objet Ruby peut avoir des variables d'instance, donc les objets de classe peuvent aussi avoir des variables d'instance.

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 les variables d'instance fonctionnent - 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 souhaitiez probablement.

En d'autres termes, les variables d'instance de classe sont en quelque sorte un accident de la conception de Ruby. Vous ne devriez probablement pas les utiliser à moins de savoir spécifiquement qu'ils sont ce que vous recherchez.

Brent Royal-Gordon
la source
donc la variable de classe est comme la variable statique en Java?
Kick Buttowski le
3

FAQ officielle sur Ruby: Quelle est la différence entre les variables de classe et les variables d'instance de classe?

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, tandis que les variables d'instance de classe n'appartiennent qu'à une classe spécifique.

Les variables de classe peuvent d'une certaine manière être considérées comme des variables globales dans le contexte d'une hiérarchie d'héritage, avec tous les problèmes qui viennent avec les variables globales. Par exemple, une variable de classe pourrait (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, une classe d'ancêtre pourrait être rouverte et modifiée plus tard, avec des effets éventuellement 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 ne sachiez exactement ce que vous faites et que vous ayez explicitement besoin de ce type de comportement, il vaut mieux utiliser des variables d'instance de classe.

notapatch
la source
2

Pour ceux qui ont une formation C ++, vous pouvez être intéressé par une comparaison avec l'équivalent C ++:

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";

Comme nous pouvons le voir, kest une staticvariable similaire. C'est 100% comme une variable globale, sauf qu'elle appartient à la classe ( portée pour être correcte). Cela permet d'éviter plus facilement les conflits entre des variables de nom similaire. Comme toute variable globale, il n'y a qu'une seule instance de cette variable et sa modification est toujours visible par tous.

D'autre part, sest une valeur spécifique à un objet. Chaque objet a sa propre instance de la valeur. En C ++, vous devez créer une instance pour avoir accès à cette variable. Dans Ruby, la définition de classe est elle-même une instance de la classe (en JavaScript, cela s'appelle un prototype), vous pouvez donc y accéder sdepuis la classe sans instanciation supplémentaire. L'instance de classe peut être modifiée, mais la modification de sva être spécifique à chaque instance (chaque objet de type S). Ainsi, en modifier un ne changera pas la valeur d'un autre.

Alexis Wilke
la source
1

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érencées à la fois dans les méthodes singleton et d'instance, il y a un inconvénient majeur. Ils sont partagés et les sous-classes peuvent donc changer la valeur de la variable d'instance de classe, et la classe de base sera également affectée par le changement, ce qui est 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 les sous-classes. La valeur class_attribute est accessible à la fois dans les méthodes singleton et d'instance, comme c'est le cas avec la variable d'instance de classe. Cependant, l'énorme avantage de class_attribute dans Rails est que les sous-classes peuvent changer leur propre valeur et cela n'aura aucun 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" 
Donato
la source
Bon appel, je n'avais jamais utilisé ça avant. Cela semble fonctionner même si vous devez vous assurer de le préfixer self.chaque fois que vous souhaitez accéder à l'attribut c, par exempleself.c . La documentation indique qu'un default:paramètre peut être transmis class_attributemais cela ne semble pas fonctionner en raison du point que je viens de mentionner self.
Dex
Quand vous dites "Bien qu'il puisse sembler immédiatement utile d'utiliser des variables d'instance de classe", je pense que vous voulez dire "variables de classe", pas "variables d'instance de classe, n'est-ce pas? (Voir ruby-lang.org/en/documentation/faq/8/. )
Keith Bennett le
Oui, cette réponse confond complètement «variables d'instance de classe» et «variables de classe», ce qui est tout le point de la question.
stevo le