Що означає змінна @@ у 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
Я не знаю, але я відчуваю, що він дивиться на мене. Мені трохи страшно
кодувати

2
TL; DR для загального користування: 99 разів із 100 я використовував би змінні "class instance" ( @внутрішні 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поділяється між класами; встановлення значення в екземплярі одного змінює значення для всіх інших екземплярів цього класу і навіть дочірніх класів, де змінна назва @sharedз одним @не була б.

[Оновлення]

Як згадує Фрогз у коментарях, в 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
Ця відповідь була б ідеальною IMHO, якби ви включили код, що показує, як ви можете використовувати змінну екземпляра на рівні класу для відстеження даних рівня класу без 'дивної' поведінки обміну даними між підкласами.
Фрогз

3
Я також зазначу, що змінні класу можуть бути небезпечними / ненадійними в багатопотоковому середовищі (наприклад, Rails)
Dylan Markow

Хм ... певним чином це виглядає як статичні змінні в PHP, але частина успадкування інша. Я не думаю, що PHP має щось подібне.
Андрій

5
Я не розумію, що ruby class << self endробить цей блок, зокрема оператор <<.
davidtingsu

1
для інших, хто плутається, class << selfбачать це
kapad

37

@- змінна інстанція класу
@@- змінна класу, яка також називається статичною змінною в деяких випадках

Змінна класу - це змінна, яка поділяється між усіма примірниками класу. Це означає, що існує лише одне змінне значення для всіх об'єктів, створених з цього класу. Якщо один екземпляр об'єкта змінює значення змінної, це нове значення по суті зміниться для всіх інших екземплярів об'єкта.

Інший спосіб мислення мінливих класів - це глобальні змінні в контексті одного класу. Змінні класу оголошуються за допомогою префіксації назви змінної двома @символами ( @@). Змінні класу повинні бути ініціалізовані під час створення


10

@@ позначає змінну класу, тобто вона може бути успадкована.

Це означає, що якщо ви створите підклас цього класу, він успадкує змінну. Отже, якщо у вас є клас Vehicleзі змінною класу, @@number_of_wheelsто, якщо ви створюєте, class Car < Vehicleто він також матиме змінну класу@@number_of_wheels


Це означає, що якщо ви створите підклас цього класу, він успадкує змінну. Отже, якщо у вас є клас Vehicleзі змінною класу, @@number_of_wheelsтоді, якщо ви створюєте, class Car < Vehicleтоді він теж матиме змінну класу@@number_of_wheels
Fareesh Vijayarangam,

12
Якщо у мене є class Vehicleс @number_of_wheels, то class Car < Vehicleтакож буде мати змінну екземпляра, яку називають @number_of_wheels. Ключова відмінність змінних класів полягає в тому, що класи мають однакову змінну, наприклад, зміна одного змінює інші.
Мішель Тіллі

1

@ і @@ в модулях також працюють по-різному, коли клас розширює або включає цей модуль.

Так дано

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

Тому використовуйте @@ в модулях для змінних, які ви хочете загальні для всіх їх використання, і використовуйте @ в модулях для змінних, які ви хочете розділити для кожного контексту використання.


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, але це вже інша історія). Ви використовуєте @ notation замість @@, але не можете отримати доступ до цих змінних з методів екземпляра. Потрібно мати обгортки методу класу.

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 (з одиночним @ notation) завжди є змінною екземпляра, це не обов'язково означає, що це не єдина загальна змінна для всіх екземплярів цього класу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.