Zmienna instancji klasy Ruby vs. zmienna klasy


179

Przeczytałem „ Kiedy ustawia się zmienne instancji Ruby? ”, Ale nie wiem, kiedy użyć zmiennych instancji klasy.

Zmienne klas są wspólne dla wszystkich obiektów klasy, zmienne Instancji należą do jednego obiektu. Nie mamy wiele miejsca na użycie zmiennych instancji klasy, jeśli mamy zmienne klasy.

Czy ktoś mógłby wyjaśnić różnicę między tymi dwoma i kiedy ich używać?

Oto przykład kodu:

class S
  @@k = 23
  @s = 15
  def self.s
    @s
  end
  def self.k
     @@k
  end

end
p S.s #15
p S.k #23

Rozumiem teraz, że zmienne instancji klasy nie są przekazywane wzdłuż łańcucha dziedziczenia!

Odpowiedzi:


276

Zmienna instancji w klasie:

class Parent
  @things = []
  def self.things
    @things
  end
  def things
    self.class.things
  end
end

class Child < Parent
  @things = []
end

Parent.things << :car
Child.things  << :doll
mom = Parent.new
dad = Parent.new

p Parent.things #=> [:car]
p Child.things  #=> [:doll]
p mom.things    #=> [:car]
p dad.things    #=> [:car]

Zmienna klasy:

class Parent
  @@things = []
  def self.things
    @@things
  end
  def things
    @@things
  end
end

class Child < Parent
end

Parent.things << :car
Child.things  << :doll

p Parent.things #=> [:car,:doll]
p Child.things  #=> [:car,:doll]

mom = Parent.new
dad = Parent.new
son1 = Child.new
son2 = Child.new
daughter = Child.new

[ mom, dad, son1, son2, daughter ].each{ |person| p person.things }
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]
#=> [:car, :doll]

Dzięki zmiennej instancji w klasie (nie w instancji tej klasy) możesz przechowywać coś wspólnego dla tej klasy bez konieczności, aby podklasy automatycznie je otrzymywały (i odwrotnie). Dzięki zmiennym klasowym masz wygodę, że nie musisz pisać self.classz obiektu instancji i (gdy jest to pożądane) uzyskujesz także automatyczne udostępnianie w całej hierarchii klas.


Scalanie ich razem w jeden przykład, który obejmuje również zmienne instancji dla instancji:

class Parent
  @@family_things = []    # Shared between class and subclasses
  @shared_things  = []    # Specific to this class

  def self.family_things
    @@family_things
  end
  def self.shared_things
    @shared_things
  end

  attr_accessor :my_things
  def initialize
    @my_things = []       # Just for me
  end
  def family_things
    self.class.family_things
  end
  def shared_things
    self.class.shared_things
  end
end

class Child < Parent
  @shared_things = []
end

A potem w akcji:

mama = Parent.new
papa = Parent.new
joey = Child.new
suzy = Child.new

Parent.family_things << :house
papa.family_things   << :vacuum
mama.shared_things   << :car
papa.shared_things   << :blender
papa.my_things       << :quadcopter
joey.my_things       << :bike
suzy.my_things       << :doll
joey.shared_things   << :puzzle
suzy.shared_things   << :blocks

p Parent.family_things #=> [:house, :vacuum]
p Child.family_things  #=> [:house, :vacuum]
p papa.family_things   #=> [:house, :vacuum]
p mama.family_things   #=> [:house, :vacuum]
p joey.family_things   #=> [:house, :vacuum]
p suzy.family_things   #=> [:house, :vacuum]

p Parent.shared_things #=> [:car, :blender]
p papa.shared_things   #=> [:car, :blender]
p mama.shared_things   #=> [:car, :blender]
p Child.shared_things  #=> [:puzzle, :blocks]  
p joey.shared_things   #=> [:puzzle, :blocks]
p suzy.shared_things   #=> [:puzzle, :blocks]

p papa.my_things       #=> [:quadcopter]
p mama.my_things       #=> []
p joey.my_things       #=> [:bike]
p suzy.my_things       #=> [:doll] 

@ Phronz Jaka jest różnica między self.things a self.class.things wspomnianymi w kodzie?
cyborg

1
@cyborg self.thingsodwoływał się do metody thingsw bieżącym zakresie (w przypadku instancji klasy będzie to metoda instancji), gdzie self.class.thingsodwołuje się do thingsmetody z klasy bieżącego zakresu (ponownie w przypadku instancji klasy oznaczałoby to metoda klasy).
graffzon

Piękne wyjaśnienie.
aliahme922

30

Uważam, że główną (jedyną?) Różnicą jest dziedziczenie:

class T < S
end

p T.k
=> 23

S.k = 24
p T.k
=> 24

p T.s
=> nil

Zmienne klasy są wspólne dla wszystkich „instancji klasy” (tj. Podklas), podczas gdy zmienne instancji klasy są specyficzne tylko dla tej klasy. Ale jeśli nigdy nie zamierzasz przedłużać zajęć, różnica jest czysto akademicka.


1
To nie jedyna różnica. „Udostępniona” vs „instancja” to coś więcej niż tylko dziedziczenie. Jeśli wstawisz instancje pobierające, otrzymasz S.new.s => nili S.new.k => 23.
Andre Figueiredo

27

Źródło

Dostępność do metod instancji

  • Zmienne instancji klasy są dostępne tylko dla metod klas, a nie dla metod instancji.
  • Zmienne klasy są dostępne zarówno dla metod instancji, jak i metod klas.

Dziedziczenie

  • Zmienne instancji klasy są tracone w łańcuchu dziedziczenia.
  • Zmienne klasy nie są.
class Vars

  @class_ins_var = "class instance variable value"  #class instance variable
  @@class_var = "class variable value" #class  variable

  def self.class_method
    puts @class_ins_var
    puts @@class_var
  end

  def instance_method
    puts @class_ins_var
    puts @@class_var
  end
end

Vars.class_method

puts "see the difference"

obj = Vars.new

obj.instance_method

class VarsChild < Vars


end

VarsChild.class_method

15

Jak powiedzieli inni, zmienne klas są wspólne dla danej klasy i jej podklas. Zmienne instancji klasy należą do dokładnie jednej klasy; jego podklasy są odrębne.

Dlaczego takie zachowanie istnieje? Cóż, wszystko w Ruby jest przedmiotem - nawet klasy. Oznacza to, że każda klasa ma odpowiadający jej obiekt klasy Class(a raczej podklasę Class). (Kiedy mówisz class Foo, naprawdę deklarujesz stałą Fooi przypisujesz do niej obiekt klasy.) I każdy obiekt Ruby może mieć zmienne instancji, więc obiekty klasy również mogą mieć zmienne instancji.

Problem w tym, że zmienne instancji na obiektach klasy tak naprawdę nie zachowują się tak, jak zwykle chcesz, aby zachowały się zmienne klasy. Zwykle chcesz, aby zmienna klasy zdefiniowana w nadklasie była współużytkowana z jej podklasami, ale nie tak działają zmienne instancji - podklasa ma swój własny obiekt klasy, a ten obiekt klasy ma własne zmienne instancji. Wprowadzili więc osobne zmienne klasowe z zachowaniem, które jest bardziej prawdopodobne.

Innymi słowy, zmienne instancji klasy są jakby przypadkiem projektu Ruby. Prawdopodobnie nie powinieneś ich używać, chyba że wiesz, że są tym, czego szukasz.


więc zmienna klasowa jest jak zmienna statyczna w Javie?
Kick Buttowski

3

Oficjalne FAQ Ruby: Jaka jest różnica między zmiennymi klasowymi a zmiennymi instancji klasy?

Główną różnicą jest zachowanie dotyczące dziedziczenia: zmienne klasy są wspólne dla klasy i wszystkich jej podklas, podczas gdy zmienne instancji klasy należą tylko do jednej konkretnej klasy.

Zmienne klasowe można w pewien sposób postrzegać jako zmienne globalne w kontekście hierarchii dziedziczenia, ze wszystkimi problemami związanymi ze zmiennymi globalnymi. Na przykład zmienna klasy może (przypadkowo) zostać ponownie przypisana przez dowolną z jej podklas, wpływając na wszystkie inne klasy:

class Woof

  @@sound = "woof"

  def self.sound
    @@sound
  end
end

Woof.sound  # => "woof"

class LoudWoof < Woof
  @@sound = "WOOF"
end

LoudWoof.sound  # => "WOOF"
Woof.sound      # => "WOOF" (!)

Lub klasa przodków może być później ponownie otwarta i zmieniona, z potencjalnie zaskakującymi efektami:

class Foo

  @@var = "foo"

  def self.var
    @@var
  end
end

Foo.var  # => "foo" (as expected)

class Object
  @@var = "object"
end

Foo.var  # => "object" (!)

Tak więc, chyba że dokładnie wiesz, co robisz i wyraźnie potrzebujesz tego rodzaju zachowania, lepiej użyj zmiennych instancji klasy.


2

Dla osób z doświadczeniem w C ++ możesz być zainteresowany porównaniem z odpowiednikiem C ++:

class S
{
private: // this is not quite true, in Ruby you can still access these
  static int    k = 23;
  int           s = 15;

public:
  int get_s() { return s; }
  static int get_k() { return k; }

};

std::cerr << S::k() << "\n";

S instance;
std::cerr << instance.s() << "\n";
std::cerr << instance.k() << "\n";

Jak widzimy, kjest staticzmienną podobną. Jest to 100% jak zmiennej globalnej, oprócz tego, że jest w posiadaniu przez klasę ( określania zakresów być poprawne). Ułatwia to unikanie kolizji między zmiennymi o podobnych nazwach. Jak każda zmienna globalna, istnieje tylko jedna instancja tej zmiennej, a jej modyfikacja jest zawsze widoczna dla wszystkich.

Z drugiej strony sjest wartością specyficzną dla obiektu. Każdy obiekt ma własną instancję wartości. W C ++ musisz utworzyć instancję, aby mieć dostęp do tej zmiennej. W Ruby definicja klasy sama w sobie jest instancją klasy (w JavaScript nazywa się to prototypem), dlatego możesz uzyskać dostęp sz klasy bez dodatkowej instancji. Instancję klasy można modyfikować, ale modyfikacja sbędzie specyficzna dla każdej instancji (każdego obiektu typu S). Zatem modyfikowanie jednego nie zmieni wartości w innym.


1

Chociaż może wydawać się natychmiastowe użyteczne wykorzystanie zmiennych instancji klasy, ponieważ zmienne instancji klasy są wspólne dla podklas i można się do nich odwoływać zarówno w metodach singleton, jak i instancjach, istnieje pewna wada. Są one wspólne, więc podklasy mogą zmieniać wartość zmiennej instancji klasy, a zmiana na klasę podstawową również będzie miała wpływ, co zwykle jest niepożądanym zachowaniem:

class C
  @@c = 'c'
  def self.c_val
    @@c
  end
end

C.c_val
 => "c" 

class D < C
end

D.instance_eval do 
  def change_c_val
    @@c = 'd'
  end
end
 => :change_c_val 

D.change_c_val
(irb):12: warning: class variable access from toplevel
 => "d" 

C.c_val
 => "d" 

Railsy wprowadzają przydatną metodę o nazwie class_attribute. Jak sama nazwa wskazuje, deklaruje atrybut na poziomie klasy, którego wartość jest dziedziczona przez podklasy. Dostęp do wartości atrybutu class_attribute można uzyskać zarówno w metodach singleton, jak i instancji, podobnie jak w przypadku zmiennej instancji klasy. Jednak ogromną zaletą class_attribute w Railsach jest to, że podklasy mogą zmieniać swoją wartość i nie wpłynie to na klasę nadrzędną.

class C
  class_attribute :c
  self.c = 'c'
end

 C.c
 => "c" 

class D < C
end

D.c = 'd'
 => "d" 

 C.c
 => "c" 

Dobra rozmowa, nie korzystałem z tego wcześniej. Wydaje się, że działa, ale musisz pamiętać, aby przygotować się za self.każdym razem, gdy chcesz uzyskać dostęp do atrybutu c, np self.c. Dokumenty mówią, że default:parametr można przekazać, class_attributeale wydaje się, że nie działa ze względu na punkt, o którym właśnie wspomniałem self.
Dex

Kiedy mówisz „Chociaż korzystanie ze zmiennych instancji klasy może natychmiast wydawać się przydatne”, myślę, że masz na myśli „zmienne klasy”, a nie „zmienne instancji klasy, prawda? (Zobacz ruby-lang.org/en/documentation/faq/8/. )
Keith Bennett

Tak, ta odpowiedź całkowicie myli „zmienne instancji klasy” i „zmienne klasy”, co stanowi sedno pytania.
stevo
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.