O que a variável @@ significa em Ruby?

162

O que são variáveis de Ruby precedido com o dobro de sinais ( @@)? Meu entendimento de uma variável precedida por um sinal de arroba é que ela é uma variável de instância, como esta no PHP:

Versão PHP

class Person {

    public $name;

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

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

Equivalente a Ruby

class Person

    def set_name(name)
        @name = name
    end

    def get_name()
        @name
    end
end

O que significa o dobro no sinal @@e como ele difere de um único sinal?

Andrew
fonte
103
Não sei, mas sinto que está me encarando. Estou um pouco assustado ao código em Ruby agora ...
corsiKa
2
TL; DR para o público: 99 vezes em 100, eu usaria variáveis ​​de "instância de classe" ( métodos @internos self) e não variáveis ​​de classe ( @@). Veja a litania de razões pelas quais as respostas abaixo.
precisa saber é o seguinte

Respostas:

240

Uma variável prefixada com @é uma variável de instância , enquanto uma prefixada com @@é uma variável de classe . Confira o exemplo a seguir; sua saída está nos comentários no final das putslinhas:

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

Você pode ver que @@sharedé compartilhado entre as classes; definir o valor em uma instância de um altera o valor para todas as outras instâncias dessa classe e até mesmo classes filho, onde uma variável chamada @shared, com um @, não seria.

[Atualizar]

Como Phrogz menciona nos comentários, é um idioma comum no Ruby rastrear dados em nível de classe com uma variável de instância na própria classe . Esse pode ser um assunto difícil de entender, e há muitas leituras adicionais sobre o assunto, mas pense nisso como modificar a Classclasse, mas apenas a instância da Classclasse com a qual você está trabalhando. Um exemplo:

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í o Squareexemplo (que resultados nil) para demonstrar que isso pode não se comportar 100% conforme o esperado; o artigo que vinculei acima tem muitas informações adicionais sobre o assunto.

Lembre-se também de que, como na maioria dos dados, você deve ser extremamente cuidadoso com as variáveis ​​de classe em um ambiente multithread , conforme comentário de dmarkow.

Michelle Tilley
fonte
1
Esta resposta seria IMHO perfeita se você incluísse código mostrando como você pode usar uma variável de instância no nível da classe para rastrear dados no nível da classe sem o comportamento "estranho" de compartilhar dados entre subclasses.
Phrogz 4/11/11
3
Eu também salientar que as variáveis de classe pode ser perigoso / não confiável em um ambiente multi-threaded (por exemplo Rails)
Dylan Markow
Hmm ... de certa forma parece variáveis ​​estáticas no PHP, mas a parte da herança é diferente. Eu não acho que o PHP tenha algo exatamente assim.
Andrew
5
Não entendo o que o ruby class << self endbloco faz, especificamente o operador <<.
Davidtingsu
1
para os outros que estão confusos sobre class << selfver este
kapad
37

@- Variável de instância de uma classe
@@- Variável de classe, também chamada como variável estática em alguns casos

Uma variável de classe é uma variável compartilhada entre todas as instâncias de uma classe. Isso significa que existe apenas um valor variável para todos os objetos instanciados nesta classe. Se uma instância do objeto alterar o valor da variável, esse novo valor será alterado essencialmente para todas as outras instâncias do objeto.

Outra maneira de pensar sobre as variáveis ​​de classe é como variáveis ​​globais no contexto de uma única classe. As variáveis ​​de classe são declaradas prefixando o nome da variável com dois @caracteres ( @@). Variáveis ​​de classe devem ser inicializadas no momento da criação

Shaunak
fonte
10

@@ denota uma variável de classe, ou seja, pode ser herdada.

Isso significa que, se você criar uma subclasse dessa classe, ela herdará a variável. Portanto, se você tiver uma classe Vehiclecom a variável de classe @@number_of_wheels, se criar uma class Car < Vehicle, ela também terá a variável de classe@@number_of_wheels

Fareesh Vijayarangam
fonte
Isso significa que, se você criar uma subclasse dessa classe, ela herdará a variável. Então, se você tem uma classe Vehiclecom a variável de classe @@number_of_wheels, em seguida, se você criar um class Car < Vehicle, em seguida, ele também terá a variável de classe@@number_of_wheels
Fareesh Vijayarangam
12
Se eu tiver um class Vehiclecom @number_of_wheels, class Car < Vehicletambém terá uma variável de instância chamada @number_of_wheels. A principal diferença com as variáveis ​​de classe é que as classes têm a mesma variável, por exemplo, alterar uma altera as outras.
Michelle Tilley
1

@ e @@ nos módulos também funcionam de maneira diferente quando uma classe estende ou inclui esse módulo.

Tão 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 

Então você obtém as saídas abaixo mostradas como comentários

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

Portanto, use @@ in modules para variáveis ​​que você deseja que sejam comuns a todos os seus usos, e use @ in modules para variáveis ​​que você deseja separar para cada contexto de uso.

Tantallion
fonte
1

As respostas estão parcialmente corretas porque @@ é realmente uma variável de classe que é por hierarquia de classes, o que significa que é compartilhada por uma classe, suas instâncias e suas classes descendentes e suas instâncias.

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

Isso produzirá

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

Portanto, existe apenas uma mesma variável @@ para as classes Person, Student e Graduate e todos os métodos de classe e instância dessas classes se referem à mesma variável.

Existe outra maneira de definir uma variável de classe que é definida em um objeto de classe (lembre-se de que cada classe é na verdade uma instância de algo que na verdade é a classe Class, mas é outra história). Você usa a notação @ em vez de @@, mas não pode acessar essas variáveis ​​nos métodos da instância. Você precisa ter wrappers de método 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(",")

Aqui, @people é único por classe, em vez de hierarquia de classes, porque na verdade é uma variável armazenada em cada instância de classe. Esta é a saída:

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

Uma diferença importante é que, você não pode acessar essas variáveis ​​de classe (ou variáveis ​​de instância de classe, você pode dizer) diretamente dos métodos de instância, porque @people em um método de instância se refere a uma variável de instância dessa instância específica das classes Person, Student ou Graduate .

Portanto, enquanto outras respostas afirmam corretamente que @myvariable (com notação @ única) é sempre uma variável de instância, isso não significa necessariamente que não é uma única variável compartilhada para todas as instâncias dessa classe.

Cagatay Kalan
fonte