- PVSM.RU - https://www.pvsm.ru -

Class-level instance variables

В последнее время было много публикаций на эту тему, но у новичков снова и снова возникают вопросы. Публикую свой вариант обьяснения почему не работает как ожидается переменная экземпляра, объявленная в теле класса:

class User
  @var = 1
end

Ожидаемое поведение

Переменные экземпляра работают так, как от них ожидают в обычных ситуациях:

class A
  def set_a( v );  @a = v;  end
  def get_a; return @a; end
end

o1 = A.new
o1.instance_variables   # => []
o1.get_a   # => nil

o1.set_a 123
o1.instance_variables   # => ["@a"]
o1.get_a   # => 123

o2 = A.new
o2.get_a   # => nil

Переменная появляется в экземпляре после ее установки, дальше все работает как по маслу — она видна только в своем экземпляре.

Значение по-умолчанию

Неожиданности с переменныой экземпляра обычно начинаются когда нужно установить значение ее по-умолчанию:

class B
  attr_accessor :b       # вместо set_b, get_b из вышестоящего примера
  @b = 1
end

o1 = B.new
o1.b    # => nil
o1.instance_variables   # => []

o1.b = 2
o1.b    # => 2
o1.instance_variables   # => ["@b"]

Оп-па. А где же наше значение по-умолчанию? Почему переменной вообще нет в обьекте с самого начала?

Такие переменные называются Class-Level Instance Variable (как это по-русски «переменные экземпляра Class-уровня»?)

Классы это объекты.

На Ruby, что любой код выполняется в контексте текущего обьекта (экземпляра класса), текущий объект всегда доступен из псевдо-переменной self. Это базовые истинины. Но попорбуйте ответить на вопрос, в каком конексте (объекте) выполняется строка 4 и строка 2?

1: class User
2:   @b = 1
3:   def a
4:     @a = 1
5:   end
6: end

Строка 4 выполнится в экземпляре класса User, как мы и ожидаем, в объекте который вы создадите через User.new

А строка 2 выполняется в экземпляре класса Class, в объекте который описывает класс User. Этот объект создатся сразу после чтения интерпретатором класса User.
Само слово User это не что иное как консанта, значение которой объект типа Class [1]. Этот объект описывает наш класс User. Именно к этому объекту относится переменная @b.

o1 = B.new
o1.b    # => nil
o1.instance_variables   # => []

B.instance_variables    # => ["@b"]

На самом деле она всегда была там. Не в классе B и не в экземпляре класса B, а в экземпляре класса Class хранимом в константе B.

Применение

Что нам дают переменные экземпляра Class-уровня?

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

class B
  @b = 1
  def self.set_b( v ); @b = v;  end
  def self.get_b; @b; end
end

B.get_b   # => 1

B.set_b 2
B.get_b   # => 2

class B1 < B; end

B1.get_b  # => 1
B1.set_b 3
B1.get_b   # => 3

B.get_b    # => 2

То есть при каждом наследовании от класса, в котором была объявлена такая переменная будет создаваться новая независимая. Сравните с поведением переменной класса:

class C
  @@c = 1
  def self.set_c( v ); @@c = v;  end
  def self.get_c; @@c; end
end

C.get_c   # => 1

C.set_b 2
C.get_b   # => 2

class C1 < C; end
C1.get_c  # => 2

C1.set_c 3
C1.get_c   # => 3
C.get_c    # => 3

class C2 < C; @@c = 4; end
C.get_c   # => 4
C1.get_c   # => 4
C2.get_c   # => 4

Accessors

Такие переменные с помощью акцессоров создаются следующим образом:

class A
  class << self
    attr_accessor :a
  end
  # Вместо этого такая строка: self.class.attr_accessor :a
  # невозможна только потому, что attr_accessor это private method :(
end

A.instance_variables # => ["@a"]
A.class_variables    # => []
A.a   # => nil

A.a = 123
A.a   # => 123

Обратите внимание что рельсовый cattr_accessor :a создаст методы с такими же именами, только обращаться они будут к переменной класса.

class User
  cattr_accessor :a
end

User.instance_variables  # => []
User.class_variables     # => ["@@a"]

Материалы по теме:

railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/ [2]
wiseheartdesign.com/articles/2006/09/22/class-level-instance-variables/ [3]
snippets.dzone.com/posts/show/4382 [4]

Уважаемые читатели, если не трудно, накидайте сюда еще ссылок по теме, чтобы можно было использовать этот материал как учебный по теме.

Автор: dapi

Источник [5]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/ruby/20655

Ссылки в тексте:

[1] консанта, значение которой объект типа Class: http://habrahabr.ru/blogs/ruby/111738/

[2] railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/: http://railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/

[3] wiseheartdesign.com/articles/2006/09/22/class-level-instance-variables/: http://wiseheartdesign.com/articles/2006/09/22/class-level-instance-variables/

[4] snippets.dzone.com/posts/show/4382: http://snippets.dzone.com/posts/show/4382

[5] Источник: http://habrahabr.ru/post/111781/