Змінна екземпляра класу Ruby порівняно зі змінною класу


179

Я читав " Коли встановлюються змінні екземплярів Ruby? ", Але мені здається, коли слід використовувати змінні екземплярів класу.

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

Чи може хтось пояснити різницю між цими двома та коли їх використовувати?

Ось приклад коду:

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

Я зараз розумію, змінні інстанції класу не передаються по ланцюжку спадкування!

Відповіді:


276

Змінна інстанція для класу:

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]

Змінна класу:

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]

Зі змінною екземпляра класу (а не з примірником цього класу) ви можете зберігати щось спільне для цього класу, не маючи підкласів автоматично, також отримувати їх (і навпаки). За допомогою змінних класів ви маєте зручність не потрібно писати self.classз об’єкта екземпляра, і (коли це бажано), ви також отримуєте автоматичний обмін по всій ієрархії класів.


Об’єднання їх разом в єдиний приклад, який також охоплює змінні екземпляри на примірниках:

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

А потім у дії:

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 Яка різниця між self.things та self.class.things, які ви згадали в коді?
кіборг

1
@cyborg self.thingsпосилається на метод thingsу поточній області (у випадку екземпляра класу, це буде метод екземпляра), де self.class.thingsпосилається на thingsметод із класу поточного сфери дії (знову ж у випадку екземпляра класу це означатиме метод класу).
графзон

Красиве пояснення.
aliahme922

30

Я вважаю, що головне (лише?) Інше - спадкування:

class T < S
end

p T.k
=> 23

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

p T.s
=> nil

Змінні класу поділяються всіма "екземплярами класу" (тобто підкласами), тоді як змінні екземплярів класу є специфічними лише для цього класу. Але якщо ви ніколи не збираєтесь розширювати свій клас, різниця суто академічна.


1
Це не єдина різниця. Екземпляр "спільний" проти "йде більше, ніж просто успадкування. Якщо поставити екземпляри getters, ви отримаєте S.new.s => nilі S.new.k => 23.
Андре Фігейредо

27

Джерело

Наявність методів екземпляра

  • Змінні екземплярів класу доступні лише для методів класу, а не для методів екземплярів.
  • Змінні класу доступні як методам екземплярів, так і методам класів.

Спадковість

  • Змінні екземплярів класу втрачаються у ланцюжку спадкування.
  • Змінні класу - ні.
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

Як говорили інші, змінні класу поділяються між даним класом та його підкласами. Змінні екземплярів класу належать саме до одного класу; її підкласи окремі.

Чому така поведінка існує? Що ж, у Ruby все є об’єктом - навіть класи. Це означає, що кожен клас має відповідний йому об'єкт класу Class(вірніше, підклас Class). (Коли ви говорите class Foo, ви дійсно декларуєте константу Fooі присвоюєте їй об'єкт класу.) І кожен об’єкт Ruby може мати змінні екземпляри, тому об’єкти класу можуть мати і змінні екземпляра.

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

Іншими словами, змінні екземплярів класу є своєрідною випадковістю дизайну Ruby. Напевно, ви не повинні їх використовувати, якщо ви конкретно не знаєте, що саме ви шукаєте.


так змінна клас схожа на статичну змінну на Java?
Кіт Бутовський

3

Офіційні питання про Ruby: в чому різниця між змінними класу та змінними екземплярів класу?

Основна відмінність полягає у поведінці щодо успадкування: змінні класу поділяються між класом та усіма його підкласами, тоді як змінні екземплярів класу належать лише до одного конкретного класу.

Змінні класу певним чином можна розглядати як глобальні змінні в контексті ієрархії спадкування з усіма проблемами, що виникають із глобальними змінними. Наприклад, змінна класу може (випадково) перепризначити будь-який її підклас, що впливає на всі інші класи:

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

Або пізніше клас предків може бути знову відкритий і змінений, можливо, з дивовижними наслідками:

class Foo

  @@var = "foo"

  def self.var
    @@var
  end
end

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

class Object
  @@var = "object"
end

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

Отже, якщо ви точно не знаєте, що ви робите, і явно не потребуєте такої поведінки, вам краще використовувати змінні екземплярів класу.


2

Для тих, хто має C ++, ви можете зацікавити порівнянням із еквівалентом 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";

Як ми бачимо, kце staticподібна змінна. Це на 100% схоже на глобальну змінну, за винятком того, що вона належить класу ( визначається, щоб бути правильним). Це полегшує уникнення сутичок між аналогічно названими змінними. Як і будь-яка глобальна змінна, є лише один екземпляр цієї змінної, і її зміна завжди видно всім.

З іншого боку, sце об'єктне значення. У кожного об’єкта є свій екземпляр значення. У C ++ ви повинні створити екземпляр, щоб мати доступ до цієї змінної. У Ruby визначення класу є самим екземпляром класу (в JavaScript це називається прототипом), тому ви можете отримати доступ sз класу без додаткових інстанцій. Екземпляр класу може бути модифікований, але модифікація sбуде специфічною для кожного екземпляра (кожного об'єкта типу S). Таким чином, зміна одного не змінить значення в іншому.


1

Хоча одразу може здатися корисним використання змінних екземплярів класу, оскільки змінна екземпляра класу поділяється між підкласами, і їх можна посилатись як в одиночному, так і в екземплярному методах, є особливий недолік. Вони поділяються, і тому підкласи можуть змінювати значення змінної екземпляра класу, і базовий клас також впливатиме на зміну, яка зазвичай є небажаною поведінкою:

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" 

Rails представляє зручний метод під назвою class_attribute. Як випливає з назви, він оголошує атрибут рівня класу, значення якого успадковується підкласами. Доступ до значення class_attribute можна отримати як в однотонних, так і в методах екземплярів, як це має місце зі змінною екземпляра класу. Однак величезною перевагою class_attribute в Rails є те, що підкласи можуть змінити власне значення, і це не вплине на батьківський клас.

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

 C.c
 => "c" 

class D < C
end

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

 C.c
 => "c" 

Добрий дзвінок, я раніше цього не використовував. Здається, це працює, хоча вам потрібно обов’язково передбачити self.кожен раз, коли ви хочете отримати доступ до атрибуту c, наприклад self.c. Документи кажуть, що default:параметр можна передати, class_attributeале він, здається, не працює через точку, про яку я тільки що згадав self.
Декс

Коли ви говорите "Хоча це може відразу здатися корисним для використання змінних екземплярів класу", я думаю, ви маєте на увазі "змінні класу", а не "змінні екземпляри класу правильно?" (Див. Ruby-lang.org/en/documentation/faq/8/. )
Кіт Беннетт

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