Co oznacza zmienna @@ w Rubim?

162

Co to są zmienne Ruby poprzedzone podwójnym znakiem ( @@)? Moje rozumienie zmiennej poprzedzonej znakiem at jest takie, że jest to zmienna instancji, tak jak w PHP:

Wersja PHP

class Person {

    public $name;

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

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

Odpowiednik Ruby

class Person

    def set_name(name)
        @name = name
    end

    def get_name()
        @name
    end
end

Co oznacza podwójny znak @@i czym różni się od pojedynczego znaku?

Andrzej
źródło
103
Nie wiem, ale mam wrażenie, że się na mnie gapi. Trochę się boję kodowania w Rubim ...
corsiKa
2
TL; DR dla publiczności: 99 razy na 100, użyłbym zmiennych „instancji klasy” ( @wewnątrz selfmetod), a nie zmiennych klas ( @@). Zobacz litanię powodów w poniższych odpowiedziach.
WattsInABox

Odpowiedzi:

240

Zmienna z prefiksem @jest zmienną instancji , a zmienna z prefiksem @@jest zmienną klasową . Sprawdź następujący przykład; jego wynik jest w komentarzach na końcu putslinii:

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

Możesz zobaczyć, że @@sharedjest to współdzielone między klasami; ustawienie wartości w jednej instancji zmienia wartość dla wszystkich innych instancji tej klasy, a nawet klas podrzędnych, w przypadku których zmienna o nazwie @sharedz jedynką @nie byłaby.

[Aktualizacja]

Jak wspomina Phrogz w komentarzach, w Rubim powszechnym idiomem jest śledzenie danych na poziomie klasy za pomocą zmiennej instancji w samej klasie . Może to być trudny temat do ogarnięcia umysłem i jest wiele dodatkowych lektur na ten temat, ale pomyśl o tym jako o modyfikacji Classklasy, ale tylko o wystąpieniu Classklasy, z którą pracujesz. Przykład:

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

Podałem Squareprzykład (które wyniki nil), aby zademonstrować, że może to nie zachowywać się w 100% zgodnie z oczekiwaniami; artykuł I połączone powyżej ma wiele dodatkowych informacji na ten temat.

Należy również pamiętać, że podobnie jak w przypadku większości danych, należy zachować szczególną ostrożność ze zmiennymi klas w środowisku wielowątkowym , zgodnie z komentarzem dmarkow.

Michelle Tilley
źródło
1
Ta odpowiedź byłaby idealna IMHO, gdybyś dołączył kod pokazujący, jak możesz użyć zmiennej instancji na poziomie klasy do śledzenia danych na poziomie klasy bez „dziwnego” zachowania polegającego na udostępnianiu danych między podklasami.
Phrogz
3
Zwróciłbym również uwagę, że zmienne klasowe mogą być niebezpieczne / zawodne w środowisku wielowątkowym (np. W Railsach)
Dylan Markow
Hmm ... w pewnym sensie to brzmi jak zmienne statyczne w PHP, ale część dotycząca dziedziczenia jest inna. Nie sądzę, żeby PHP miało coś takiego.
Andrew,
5
Nie rozumiem, co ruby class << self endrobi blok, a konkretnie operator <<.
davidtingsu
1
dla innych, którzy są zdezorientowani, class << selfzobacz to
kapad
37

@- Zmienna instancji klasy
@@- Zmienna klasy, w niektórych przypadkach nazywana także zmienną statyczną

Zmienna klasy to zmienna, która jest wspólna dla wszystkich instancji klasy. Oznacza to, że dla wszystkich obiektów utworzonych z tej klasy istnieje tylko jedna wartość zmiennej. Jeśli jedna instancja obiektu zmieni wartość zmiennej, ta nowa wartość zasadniczo zmieni się dla wszystkich innych instancji obiektu.

Inny sposób myślenia o zmiennych klasowych to zmienne globalne w kontekście pojedynczej klasy. Zmienne klas są deklarowane poprzez poprzedzenie nazwy zmiennej dwoma @znakami ( @@). Zmienne klasy muszą zostać zainicjowane w czasie tworzenia

Shaunak
źródło
10

@@ oznacza zmienną klasową, tzn. można ją dziedziczyć.

Oznacza to, że jeśli utworzysz podklasę tej klasy, odziedziczy ona zmienną. Więc jeśli masz klasę Vehicleze zmienną class, @@number_of_wheelsto jeśli utworzysz class Car < Vehicleklasę, to również będzie miała zmienną class@@number_of_wheels

Fareesh Vijayarangam
źródło
Oznacza to, że jeśli utworzysz podklasę tej klasy, odziedziczy ona zmienną. Więc jeśli masz klasę Vehicleze zmienną class, @@number_of_wheelsto jeśli utworzysz zmienną klasową, class Car < Vehicleto również będzie ona zawierała zmienną klasy@@number_of_wheels
Fareesh Vijayarangam
12
Jeśli mam class Vehiclewith @number_of_wheels, to class Car < Vehiclebędę miał również zmienną wystąpienia o nazwie @number_of_wheels. Kluczowa różnica w stosunku do zmiennych klasowych polega na tym, że klasy mają tę samą zmienną, np. Zmiana jednej powoduje zmianę pozostałych.
Michelle Tilley
1

@ i @@ w modułach również działają inaczej, gdy klasa rozszerza lub zawiera ten moduł.

Tak biorąc

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 

Następnie otrzymujesz wyniki poniżej pokazane jako komentarze

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

Więc używaj @@ w modułach dla zmiennych, które chcesz, aby były wspólne dla wszystkich ich zastosowań, a @ w modułach dla zmiennych, które chcesz osobno dla każdego kontekstu użycia.

Tantallion
źródło
1

Odpowiedzi są częściowo poprawne, ponieważ @@ jest w rzeczywistości zmienną klasową, która jest według hierarchii klas, co oznacza, że ​​jest wspólna dla klasy, jej instancji i jej klas podrzędnych oraz ich instancji.

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

To wyjdzie

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

Jest więc tylko jedna ta sama zmienna @@ dla klas Person, Student i Graduate, a wszystkie metody klas i instancji tych klas odwołują się do tej samej zmiennej.

Istnieje inny sposób definiowania zmiennej klasy, która jest zdefiniowana w obiekcie klasy (pamiętaj, że każda klasa jest w rzeczywistości instancją czegoś, co w rzeczywistości jest klasą Class, ale jest to inna historia). Używasz notacji @ zamiast @@, ale nie możesz uzyskać dostępu do tych zmiennych z metod instancji. Musisz mieć opakowania metod klas.

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

Tutaj @people jest pojedyncza na klasę, a nie w hierarchii klas, ponieważ w rzeczywistości jest to zmienna przechowywana w każdej instancji klasy. Oto wynik:

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

Jedną ważną różnicą jest to, że nie można uzyskać dostępu do tych zmiennych klas (lub zmiennych instancji klasy, które można powiedzieć) bezpośrednio z metod instancji, ponieważ @people w metodzie instancji odwołuje się do zmiennej instancji tej konkretnej instancji klas Person, Student lub Graduate .

Tak więc, podczas gdy inne odpowiedzi poprawnie stwierdzają, że @myvariable (z pojedynczą notacją @) jest zawsze zmienną instancji, nie musi to oznaczać, że nie jest to pojedyncza zmienna wspólna dla wszystkich instancji tej klasy.

Cagatay Kalan
źródło