2014-01-03 7 views
2

Я просто потратил кучу времени, играя в IRB (ну, PRY на самом деле), пытаясь выяснить, как переменные класса работают в Ruby, и я полностью озадачен тем, что нашел.Почему переменные класса Ruby ведут себя так странно?

Из того, что я видел (исправьте меня, если я ошибаюсь здесь), переменные класса разделяются между классами, экземплярами этих классов, подклассами и экземплярами подклассов. Однако для подклассов и их экземпляров переменная класса делится только с суперклассом, если переменной класса присвоено суперкласс. Если он не был назначен в суперклассе, он остается неопределенным там до тех пор, пока ему не будет присвоено значение, после чего переменная станет общедоступной ... Что за черт? (См. Пример ниже, если вы запутались.)

Так почему же это? Я где-то слышал, что переменные класса Ruby основаны на некоторой аналогичной концепции в Smalltalk, но я действительно вне себя от того, почему такое поведение было бы желательно.


Пример:

В superfoo_and_subbar.rb:

class SuperFoo 
    def class_var_x=(x) 
    @@x = x 
    end 
    def class_var_x 
    @@x 
    end 
end 

class SubBar < SuperFoo 
    # Define these again, just in case they're bound at compile time or something... 
    def class_var_x=(x) 
    @@x = x 
    end 
    def class_var_x 
    @@x 
    end 
end 

В PRY (или) IRB сессии:

# Okay, let's do this! 

require './superfoo_and_subbar' # => true 

SuperFoo.new.class_var_x # NameError: uninitialized class variable @@x in SuperFoo 
SubBar.new.class_var_x # NameError: uninitialized class variable @@x in SubBar 

# Okay, no suprise there, let's define the class variable on SuperFoo 
SuperFoo.new.class_var_x = 1 
SuperFoo.new.class_var_x # => 1 

# Okay, looks about right. What does bar say now? 

SubBar.new.class_var_x # => 1 

# Okay, that's pretty weird but I did hear that Ruby class variables behave 
# that way, so no big deal. 

SubBar.new.class_var_x = 2 
SubBar.new.class_var_x # => 2 
SuperFoo.new.class_var_x # => 2 

# Right, so these both point to the same variable then. 

Новая Поддеть сессия:

# Now let's try this again: 

require './superfoo_and_subbar' # => true 

SuperFoo.new.class_var_x # NameError: uninitialized class variable @@x in SuperFoo 
SubBar.new.class_var_x # NameError: uninitialized class variable @@x in SubBar 

# So far so good. Let's set x on SubBar first this time 

SubBar.new.class_var_x = 2 
SubBar.new.class_var_x # => 2 

# Okay, so x is now set on SubBar so it should also be set on SuperFoo then, right? 

SuperFoo.new.class_var_x # NameError: uninitialized class variable @@x in SuperFoo 

# Wait, what? So they're seperate variables now? 

SubBar.new.class_var_x # => 2 
SuperFoo.new.class_var_x # NameError: uninitialized class variable @@x in SuperFoo 

# It certainly looks that way. What happens if I set x on SuperFoo then? 

SuperFoo.new.class_var_x = 3 

SuperFoo.new.class_var_x # => 3 
SubBar.new.class_var_x # => 3 

# Wait, so now they're the same varaible again? What the heck? 

SubBar.new.class_var_x = 4 

SuperFoo.new.class_var_x # => 4 
SubBar.new.class_var_x # => 4 

# ...WHY!?!? Seriously, what's the point of this? 
+1

Это ведет себя так из-за переписанных методов в SubBar.Если он был подклассифицирован как обычный: 'SubBar steenslag

+0

@steenslag Я бы не сказал «как бы ожидал», потому что, честно говоря, мой первый инстинкт был бы для переменных класса просто синтаксическим сахаром, например переменными класса. Вы правы, хотя не переопределяя эти два метода, мой второй пример ведет себя как первый. – Ajedi32

+0

@Arup Я просто заметил, что удивительное поведение не происходит, когда переопределенные методы удаляются из кода. (Пример David A Black меня совсем не удивил! Беспокойство ...) – steenslag

ответ

2

Так наследование работает. Да переменные класса разделяются на свой подкласс (ы), как только суперкласс определит его. SubBar.new.class_var_x создал переменную класса только в классе SubBar. Поэтому ясно, что его суперкласс не будет иметь к нему доступа.

class Foo 
    def x_class_var=(val) 
    @@x =val 
    end 
    def x_class_var 
    @@x 
    end 
end 

class Bar<Foo 
    def x_class_var=(val) 
    @@x =val 
    end 
    def x_class_var 
    @@x 
    end 
end 

Foo.class_variables # => [] 
Bar.class_variables # => [] 
Bar.new.x_class_var = 10 
Foo.class_variables # => [] 
Bar.class_variables # => [:@@x] 

Bar.new.x_class_var создал переменную класса @@x в Bar классе, и Foo быть суперкласс Bar не будет иметь доступ к этому @@x. Теперь посмотрим другой путь -

Foo.class_variables # => [] 
Bar.class_variables # => [] 
Foo.new.x_class_var = 10 
Foo.class_variables # => [:@@x] 
Bar.class_variables # => [:@@x] 

Теперь @@x создается в Foo классе, так быть подклассом Foo, Bar имеет доступ к @@x. Это связано с тем, что переменные класса разделяются от суперкласса к подклассу, а не от подкласса до суперкласса, как показано выше в примере.

+0

Спасибо, это довольно хорошая ментальная модель работы переменных класса. На самом деле, хотя с этим вопросом меня больше интересует «почему», чем с «как». (Например, почему Ruby делает это так? Есть ли причина, по которой кто-либо захочет использовать переменные класса таким образом?) В целом, это довольно полезный ответ. : +1: – Ajedi32

1

В первой сессии PRY, когда вы проверили SubBar.new.class_var_x, она проверила, существует ли @@class_var_x. Так как он существует в SuperFoo, и SubBar < SuperFoo, это заняло SuperFoo's @@class_var_x.

Во второй сессии, когда вы проверили SuperFoo.new.class_var_x, он не взгляда на SubBar «s @@class_var_x, потому что объекты родительских классов не заботятся о классах, наследующих от них, так что создается новый экземпляр @@class_var_x.

+0

Но тогда я назначил 'SuperFoo.new.class_var_x', и вдруг' SubBar.new.class_var_x' ссылается на значение из 'SuperFoo' вместо значения, которое мне было присвоено ранее? – Ajedi32

Смежные вопросы