Что означает переменная @@ в Ruby?

162

Какие переменные Ruby начинаются с двойного в знаках ( @@)? Мое понимание переменной, которой предшествует знак at, заключается в том, что это переменная экземпляра, как в PHP:

Версия PHP

class Person {

    public $name;

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

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

Рубиновый эквивалент

class Person

    def set_name(name)
        @name = name
    end

    def get_name()
        @name
    end
end

Что означает двойное в знаке @@, и как оно отличается от единственного в знаке?

Андрей
источник
103
Я не знаю, но у меня такое чувство, что оно смотрит на меня. Я немного напуган, чтобы кодировать в Ruby сейчас ...
CorsiKa
2
TL; DR для общественности: 99 раз из 100, я бы использовал переменные «экземпляра класса» ( @внутри selfметодов), а не переменные класса ( @@). Смотрите список причин, почему в ответах ниже.
WattsInABox

Ответы:

240

Переменная префикс @является переменной экземпляра , в то время как один префикс @@является переменной класса . Посмотрите на следующий пример; его вывод находится в комментариях в конце putsстрок:

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

Вы можете видеть, что @@sharedразделено между классами; установка значения в экземпляре one изменяет значение для всех других экземпляров этого класса и даже дочерних классов, где не будет переменной с именем @shared, с одним @.

[Обновить]

Как упоминает Phrogz в комментариях, в Ruby распространена идиома - отслеживать данные уровня класса с помощью переменной экземпляра самого класса . Это может быть сложным предметом, чтобы обернуть ваше внимание, и есть много дополнительного чтения по этому вопросу, но подумайте об этом как о модификации Classкласса, но только о экземпляре Classкласса, с которым вы работаете. Пример:

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

Я включил Squareпример (который выводит nil), чтобы продемонстрировать, что это может не вести себя на 100%, как вы ожидаете; В статье, на которую я ссылался выше, есть много дополнительной информации по этому вопросу.

Также имейте в виду, что, как и в случае с большинством данных, вы должны быть предельно осторожны с переменными класса в многопоточной среде , согласно комментарию dmarkow.

Мишель Тилли
источник
1
Этот ответ был бы идеальным ИМХО, если бы вы включили код, показывающий, как вы можете использовать переменную экземпляра на уровне класса для отслеживания данных на уровне класса без «странного» поведения обмена данными между подклассами.
Phrogz
3
Я также хотел бы отметить, что переменные класса могут быть опасными / ненадежными в многопоточной среде (например, Rails)
Дилан Марков
Хм ... в некотором смысле это звучит как статические переменные в PHP, но часть наследования отличается. Я не думаю, что в PHP есть нечто подобное.
Андрей
5
Я не понимаю, что ruby class << self endделает блок, особенно оператор <<.
Давидтингсу
1
для других, которые смущены, class << selfвидят это
kapad
37

@- переменная экземпляра класса
@@- переменная класса, в некоторых случаях также называемая статической переменной

Переменная класса - это переменная, которая используется всеми экземплярами класса. Это означает, что существует только одно значение переменной для всех объектов, созданных из этого класса. Если один экземпляр объекта изменяет значение переменной, это новое значение существенно изменится для всех остальных экземпляров объекта.

Другой способ мышления о переменных класса - это глобальные переменные в контексте одного класса. Переменные класса объявляются префиксом имени переменной двумя @символами ( @@). Переменные класса должны быть инициализированы во время создания

Shaunak
источник
10

@@ обозначает переменную класса, т.е. она может быть унаследована.

Это означает, что если вы создадите подкласс этого класса, он унаследует переменную. Так что, если у вас есть класс Vehicleс переменной класса, @@number_of_wheelsто, если вы создаете, class Car < Vehicleто он тоже будет иметь переменную класса@@number_of_wheels

Фариш Виджаярангам
источник
Это означает, что если вы создадите подкласс этого класса, он унаследует переменную. Так что, если у вас есть класс Vehicleс переменной класса, @@number_of_wheelsто, если вы создадите его, у class Car < Vehicleнего тоже будет переменная класса@@number_of_wheels
Фариш Виджаярангам
12
Если у меня есть class Vehicleс @number_of_wheels, то class Car < Vehicleтакже будет иметь имя переменной экземпляра @number_of_wheels. Основное различие с переменными класса состоит в том, что классы имеют одну и ту же переменную, например, изменение одной изменяет другие.
Мишель Тилли
1

Модули @ и @@ in также работают по-разному, когда класс расширяет или включает этот модуль.

Так дано

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 

Тогда вы получите результаты, показанные ниже в виде комментариев

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

Поэтому используйте @@ в модулях для переменных, которые вы хотите использовать для всех их применений, и используйте @ в модулях для переменных, которые вы хотите использовать отдельно для каждого контекста использования.

Tantallion
источник
1

Ответы частично верны, потому что @@ на самом деле является переменной класса для каждой иерархии классов, что означает, что она является общей для класса, его экземпляров и классов-потомков и их экземпляров.

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

Это будет выводить

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

Таким образом, существует только одна и та же переменная @@ для классов Person, Student и Graduate, и все методы классов и экземпляров этих классов ссылаются на одну и ту же переменную.

Существует другой способ определения переменной класса, которая определена для объекта класса (помните, что каждый класс на самом деле является экземпляром чего-то, что на самом деле является классом Class, но это другая история). Вы используете @ нотацию вместо @@, но вы не можете получить доступ к этим переменным из методов экземпляра. Вы должны иметь обертки метода класса.

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

Здесь @people является отдельным для класса вместо иерархии классов, потому что это фактически переменная, хранящаяся в каждом экземпляре класса. Это вывод:

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

Одно важное отличие состоит в том, что вы не можете получить доступ к этим переменным класса (или к переменным экземпляра класса, которые вы можете сказать) напрямую из методов экземпляра, поскольку @people в методе экземпляра будет ссылаться на переменную экземпляра этого конкретного экземпляра классов Person, Student или Graduate. ,

Таким образом, в то время как другие ответы правильно утверждают, что @myvariable (с одиночной нотацией @) всегда является переменной экземпляра, это не обязательно означает, что это не одна общая переменная для всех экземпляров этого класса.

Кагатай Калан
источник