Que signifie la variable @@ dans Ruby?

162

Que sont les variables Ruby précédées d'un double signe ( @@)? Ma compréhension d'une variable précédée d'un signe arobase est qu'il s'agit d'une variable d'instance, comme celle-ci en PHP:

Version PHP

class Person {

    public $name;

    public function setName($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

Équivalent rubis

class Person

    def set_name(name)
        @name = name
    end

    def get_name()
        @name
    end
end

Que signifie le double signe @@arobase et en quoi diffère-t-il d'un signe arobase unique?

Andrew
la source
103
Je ne sais pas, mais j'ai l'impression qu'il me regarde. J'ai un peu peur de coder en Ruby maintenant ...
corsiKa
2
TL; DR pour le public: 99 fois sur 100, j'utiliserais des variables "instance de classe" (à l' @intérieur des selfméthodes) et non des variables de classe ( @@). Voir la litanie des raisons dans les réponses ci-dessous.
WattsInABox

Réponses:

240

Une variable préfixée par @est une variable d'instance , tandis qu'une variable préfixée par @@est une variable de classe . Consultez l'exemple suivant; sa sortie est dans les commentaires à la fin des putslignes:

class Test
  @@shared = 1

  def value
    @@shared
  end

  def value=(value)
    @@shared = value
  end
end

class AnotherTest < Test; end

t = Test.new
puts "t.value is #{t.value}" # 1
t.value = 2
puts "t.value is #{t.value}" # 2

x = Test.new
puts "x.value is #{x.value}" # 2

a = AnotherTest.new
puts "a.value is #{a.value}" # 2
a.value = 3
puts "a.value is #{a.value}" # 3
puts "t.value is #{t.value}" # 3
puts "x.value is #{x.value}" # 3

Vous pouvez voir que @@sharedc'est partagé entre les classes; la définition de la valeur dans une instance de one change la valeur de toutes les autres instances de cette classe et même des classes enfants, là où une variable nommée @shared, avec un @, ne le serait pas.

[Mettre à jour]

Comme le mentionne Phrogz dans les commentaires, c'est un idiome courant dans Ruby pour suivre les données au niveau de la classe avec une variable d'instance sur la classe elle-même . Cela peut être un sujet délicat à comprendre, et il y a beaucoup de lectures supplémentaires sur le sujet, mais pensez-y comme une modification de la Classclasse, mais uniquement de l'instance de la Classclasse avec laquelle vous travaillez. Un exemple:

class Polygon
  class << self
    attr_accessor :sides
  end
end

class Triangle < Polygon
  @sides = 3
end

class Rectangle < Polygon
  @sides = 4
end

class Square < Rectangle
end

class Hexagon < Polygon
  @sides = 6
end

puts "Triangle.sides:  #{Triangle.sides.inspect}"  # 3
puts "Rectangle.sides: #{Rectangle.sides.inspect}" # 4
puts "Square.sides:    #{Square.sides.inspect}"    # nil
puts "Hexagon.sides:   #{Hexagon.sides.inspect}"   # 6

J'ai inclus l' Squareexemple (quels résultats nil) pour démontrer que cela peut ne pas se comporter à 100% comme prévu; l' article que j'ai lié ci-dessus contient de nombreuses informations supplémentaires sur le sujet.

Gardez également à l'esprit que, comme pour la plupart des données, vous devez être extrêmement prudent avec les variables de classe dans un environnement multithread , comme indiqué par le commentaire de dmarkow.

Michelle Tilley
la source
1
Cette réponse serait parfaite à mon humble avis si vous incluiez du code montrant comment vous pouvez utiliser une variable d'instance au niveau de la classe pour suivre les données au niveau de la classe sans le comportement «étrange» de partage de données entre les sous-classes.
Phrogz
3
Je soulignerais également que les variables de classe peuvent être dangereuses / peu fiables dans un environnement multi-thread (par exemple, Rails)
Dylan Markow
Hmm ... d'une certaine manière, cela ressemble à des variables statiques en PHP, mais la partie héritage est différente. Je ne pense pas que PHP ait quelque chose d'exactement comme ça.
Andrew
5
Je ne comprends pas ce que fait le ruby class << self endbloc, en particulier l'opérateur <<.
davidtingsu
1
pour d'autres qui sont confus à propos de class << selfvoir ceci
kapad
37

@- Variable d'instance d'une classe
@@- Variable de classe, également appelée variable statique dans certains cas

Une variable de classe est une variable partagée entre toutes les instances d'une classe. Cela signifie qu'une seule valeur de variable existe pour tous les objets instanciés à partir de cette classe. Si une instance d'objet change la valeur de la variable, cette nouvelle valeur changera essentiellement pour toutes les autres instances d'objet.

Une autre façon de penser les variables de classe est celle des variables globales dans le contexte d'une seule classe. Les variables de classe sont déclarées en préfixant le nom de la variable avec deux @caractères ( @@). Les variables de classe doivent être initialisées au moment de la création

Shaunak
la source
10

@@ désigne une variable de classe, c'est-à-dire qu'elle peut être héritée.

Cela signifie que si vous créez une sous-classe de cette classe, elle héritera de la variable. Donc, si vous avez une classe Vehicleavec la variable de classe, @@number_of_wheelssi vous créez une, class Car < Vehicleelle aura aussi la variable de classe@@number_of_wheels

Fareesh Vijayarangam
la source
Cela signifie que si vous créez une sous-classe de cette classe, elle héritera de la variable. Donc, si vous avez une classe Vehicleavec la variable de classe, @@number_of_wheelsalors si vous créez une, class Car < Vehicleelle aura aussi la variable de classe@@number_of_wheels
Fareesh Vijayarangam
12
Si j'ai un class Vehicleavec @number_of_wheels, alors class Car < Vehicleaura également une variable d'instance appelée @number_of_wheels. La principale différence avec les variables de classe est que les classes ont la même variable, par exemple, changer l'une change les autres.
Michelle Tilley
1

@ et @@ dans les modules fonctionnent également différemment lorsqu'une classe étend ou inclut ce module.

Tellement donné

module A
    @a = 'module'
    @@a = 'module'

    def get1
        @a          
    end     

    def get2
        @@a         
    end     

    def set1(a) 
        @a = a      
    end     

    def set2(a) 
        @@a = a     
    end     

    def self.set1(a)
        @a = a      
    end     

    def self.set2(a)
        @@a = a     
    end     
end 

Ensuite, vous obtenez les sorties ci-dessous affichées sous forme de commentaires

class X
    extend A

    puts get1.inspect # nil
    puts get2.inspect # "module"

    @a = 'class' 
    @@a = 'class' 

    puts get1.inspect # "class"
    puts get2.inspect # "module"

    set1('set')
    set2('set')

    puts get1.inspect # "set" 
    puts get2.inspect # "set" 

    A.set1('sset')
    A.set2('sset')

    puts get1.inspect # "set" 
    puts get2.inspect # "sset"
end 

class Y
    include A

    def doit
        puts get1.inspect # nil
        puts get2.inspect # "module"

        @a = 'class'
        @@a = 'class'

        puts get1.inspect # "class"
        puts get2.inspect # "class"

        set1('set')
        set2('set')

        puts get1.inspect # "set"
        puts get2.inspect # "set"

        A.set1('sset')
        A.set2('sset')

        puts get1.inspect # "set"
        puts get2.inspect # "sset"
    end
end

Y.new.doit

Utilisez donc @@ dans les modules pour les variables que vous voulez communes à toutes leurs utilisations, et utilisez @ dans les modules pour les variables que vous voulez séparer pour chaque contexte d'utilisation.

Tantallion
la source
1

Les réponses sont partiellement correctes car @@ est en fait une variable de classe qui est par hiérarchie de classes, ce qui signifie qu'elle est partagée par une classe, ses instances et ses classes descendantes et leurs instances.

class Person
  @@people = []

  def initialize
    @@people << self
  end

  def self.people
    @@people
  end
end

class Student < Person
end

class Graduate < Student
end

Person.new
Student.new

puts Graduate.people

Cela produira

#<Person:0x007fa70fa24870>
#<Student:0x007fa70fa24848>

Il n'y a donc qu'une seule variable @@ pour les classes Person, Student et Graduate et toutes les méthodes de classe et d'instance de ces classes font référence à la même variable.

Il existe une autre façon de définir une variable de classe qui est définie sur un objet de classe (rappelez-vous que chaque classe est en fait une instance de quelque chose qui est en fait la classe de classe mais c'est une autre histoire). Vous utilisez la notation @ au lieu de @@ mais vous ne pouvez pas accéder à ces variables à partir des méthodes d'instance. Vous devez avoir des wrappers de méthode de classe.

class Person

  def initialize
    self.class.add_person self
  end

  def self.people
    @people
  end

  def self.add_person instance
    @people ||= []
    @people << instance
  end
end

class Student < Person
end

class Graduate < Student
end

Person.new
Person.new
Student.new
Student.new
Graduate.new
Graduate.new

puts Student.people.join(",")
puts Person.people.join(",")
puts Graduate.people.join(",")

Ici, @people est unique par classe au lieu de la hiérarchie de classes car il s'agit en fait d'une variable stockée sur chaque instance de classe. Voici la sortie:

#<Student:0x007f8e9d2267e8>,#<Student:0x007f8e9d21ff38>
#<Person:0x007f8e9d226158>,#<Person:0x007f8e9d226608>
#<Graduate:0x007f8e9d21fec0>,#<Graduate:0x007f8e9d21fdf8> 

Une différence importante est que vous ne pouvez pas accéder à ces variables de classe (ou variables d'instance de classe que vous pouvez dire) directement à partir des méthodes d'instance, car @people dans une méthode d'instance ferait référence à une variable d'instance de cette instance spécifique des classes Person, Student ou Graduate .

Ainsi, alors que d'autres réponses indiquent correctement que @myvariable (avec une seule notation @) est toujours une variable d'instance, cela ne signifie pas nécessairement qu'il ne s'agit pas d'une seule variable partagée pour toutes les instances de cette classe.

Cagatay Kalan
la source