Переменная экземпляра класса 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

Теперь я понимаю, что переменные экземпляра класса не передаются по цепочке наследования!

Ответы:


277

Переменная экземпляра в классе:

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 может иметь переменные экземпляра, поэтому объекты класса также могут иметь переменные экземпляра.

Проблема в том, что переменные экземпляра на объектах класса на самом деле ведут себя не так, как вы обычно хотите, чтобы переменные класса вели себя. Обычно вы хотите, чтобы переменная класса, определенная в суперклассе, использовалась совместно с его подклассами, но это не так, как работают переменные экземпляра - у подкласса есть свой собственный объект класса, а у этого объекта класса есть свои собственные переменные экземпляра. Таким образом, они представили отдельные переменные класса с поведением, которое вы, скорее всего, захотите.

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


так переменная класса похожа на статическую переменную в Java?
Kick Buttowski

3

Официальный Ruby FAQ: В чем разница между переменными класса и переменными экземпляра класса?

Основным отличием является поведение, касающееся наследования: переменные класса совместно используются классом и всеми его подклассами, в то время как переменные экземпляра класса принадлежат только одному конкретному классу.

Переменные класса в некотором роде можно рассматривать как глобальные переменные в контексте иерархии наследования со всеми проблемами, связанными с глобальными переменными. Например, переменная класса может (случайно) быть переназначена любым из ее подклассов, затрагивая все другие классы:

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 возможен как в методе singleton, так и в экземпляре, как в случае с переменной экземпляра класса. Однако огромное преимущество 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.
Dex

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

Да, этот ответ полностью сбивает с толку «переменные экземпляра класса» и «переменные класса», и в этом весь вопрос.
Стево
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.