¿Qué significa @@ variable en Ruby?

162

¿Cuáles son las variables de Ruby precedidas con doble en signos ( @@)? Entiendo que una variable precedida por un signo at es que es una variable de instancia, como esta en PHP:

Versión PHP

class Person {

    public $name;

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

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

Equivalente de rubí

class Person

    def set_name(name)
        @name = name
    end

    def get_name()
        @name
    end
end

¿Qué significa el doble en el signo @@y en qué se diferencia de un solo en el signo?

Andrés
fuente
103
No lo sé, pero tengo la sensación de que me está mirando. Ahora tengo un poco de miedo de codificar en Ruby ...
corsiKa
2
TL; DR para el público: 99 de cada 100 veces, usaría variables de "instancia de clase" ( métodos @internos self) no variables de clase ( @@). Vea la letanía de razones en las respuestas a continuación.
WattsInABox

Respuestas:

240

Una variable con prefijo @es una variable de instancia , mientras que una con prefijo @@es una variable de clase . Mira el siguiente ejemplo; su salida está en los comentarios al final de las putslíneas:

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

Puede ver que @@sharedse comparte entre las clases; establecer el valor en una instancia de uno cambia el valor para todas las demás instancias de esa clase e incluso clases secundarias, donde una variable llamada @shared, con una @, no sería.

[Actualizar]

Como Phrogz menciona en los comentarios, es un lenguaje común en Ruby rastrear datos a nivel de clase con una variable de instancia en la clase misma . Este puede ser un tema difícil de entender, y hay mucha lectura adicional sobre el tema, pero piense en ello como una modificación de la Classclase, pero solo la instancia de la Classclase con la que está trabajando. Un ejemplo:

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

Incluí el Squareejemplo (que produce nil) para demostrar que esto puede no comportarse al 100% como esperabas; El artículo que he vinculado anteriormente tiene mucha información adicional sobre el tema.

También tenga en cuenta que, como con la mayoría de los datos, debe tener mucho cuidado con las variables de clase en un entorno multiproceso , según el comentario de dmarkow.

Michelle Tilley
fuente
1
Esta respuesta sería perfecta en mi humilde opinión si incluye un código que muestre cómo puede usar una variable de instancia a nivel de clase para rastrear datos a nivel de clase sin el comportamiento "extraño" de compartir datos entre subclases.
Phrogz
3
También señalaría que las variables de clase pueden ser peligrosas / poco confiables en un entorno de subprocesos múltiples (por ejemplo, Rails)
Dylan Markow
Hmm ... en cierto modo, suena como variables estáticas en PHP, pero la parte de herencia es diferente. No creo que PHP tenga algo exactamente como esto.
Andrew
55
No entiendo lo que hace el ruby class << self endbloque, específicamente el operador <<.
davidtingsu
1
para otros que están confundidos acerca de class << selfver esto
kapad
37

@- Variable de instancia de una clase
@@- Variable de clase, también llamada como variable estática en algunos casos

Una variable de clase es una variable que se comparte entre todas las instancias de una clase. Esto significa que solo existe un valor variable para todos los objetos instanciados de esta clase. Si una instancia de objeto cambia el valor de la variable, ese nuevo valor cambiará esencialmente para todas las demás instancias de objeto.

Otra forma de pensar en las variables de clase es como variables globales dentro del contexto de una sola clase. Las variables de clase se declaran con el prefijo del nombre de la variable con dos @caracteres ( @@). Las variables de clase deben inicializarse en el momento de la creación.

Shaunak
fuente
10

@@ denota una variable de clase, es decir, se puede heredar.

Esto significa que si crea una subclase de esa clase, heredará la variable. Entonces, si tiene una clase Vehiclecon la variable de clase, @@number_of_wheelsentonces si crea una class Car < Vehicle, también tendrá la variable de clase@@number_of_wheels

Fareesh Vijayarangam
fuente
Esto significa que si crea una subclase de esa clase, heredará la variable. Entonces, si tiene una clase Vehiclecon la variable de clase, @@number_of_wheelsentonces si crea una, class Car < Vehicleentonces también tendrá la variable de clase@@number_of_wheels
Fareesh Vijayarangam
12
Si tengo un class Vehiclecon @number_of_wheels, class Car < Vehicletambién tendrá una variable de instancia llamada @number_of_wheels. La diferencia clave con las variables de clase es que las clases tienen la misma variable, por ejemplo, cambiar una cambia las otras.
Michelle Tilley
1

Los módulos @ y @@ también funcionan de manera diferente cuando una clase extiende o incluye ese módulo.

Así dado

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 

Luego obtienes los resultados a continuación que se muestran como comentarios

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

Por lo tanto, use @@ en los módulos para las variables que desea que sean comunes a todos sus usos, y use @ en los módulos para las variables que desea separar para cada contexto de uso.

Tantallion
fuente
1

Las respuestas son parcialmente correctas porque @@ es en realidad una variable de clase que es por jerarquía de clase, lo que significa que es compartida por una clase, sus instancias y sus clases descendientes y sus instancias.

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

Esto dará salida

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

Por lo tanto, solo hay una misma variable @@ para las clases Persona, Estudiante y Graduado y todos los métodos de clase e instancia de estas clases se refieren a la misma variable.

Hay otra forma de definir una variable de clase que se define en un objeto de clase (recuerde que cada clase es en realidad una instancia de algo que en realidad es la clase Class pero es otra historia). Utiliza la notación @ en lugar de @@ pero no puede acceder a estas variables desde los métodos de instancia. Necesita tener envoltorios de métodos de clase.

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(",")

Aquí, @people es individual por clase en lugar de jerarquía de clases porque en realidad es una variable almacenada en cada instancia de clase. Esta es la salida:

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

Una diferencia importante es que no puede acceder a estas variables de clase (o variables de instancia de clase que puede decir) directamente desde los métodos de instancia porque @people en un método de instancia se referiría a una variable de instancia de esa instancia específica de las clases Person o Student o Graduate .

Entonces, mientras que otras respuestas indican correctamente que @myvariable (con notación simple @) siempre es una variable de instancia, no significa necesariamente que no sea una sola variable compartida para todas las instancias de esa clase.

Cagatay Kalan
fuente