Измените общий вопрос newbiew, но почему @game_score всегда nil?
#bowling.rb
class Bowling
@game_score = 0
def hit(pins)
@game_score = @game_score + pins
end
def score
@game_score
end
end
Измените общий вопрос newbiew, но почему @game_score всегда nil?
#bowling.rb
class Bowling
@game_score = 0
def hit(pins)
@game_score = @game_score + pins
end
def score
@game_score
end
end
Пропустить через код, будем ли мы?
#bowling.rb
class Bowling
@game_score = 0 # (1)
В этот момент (1) мы все еще находимся внутри класса Bowling
. Помните: классы - это просто объекты, подобные любым другим. Итак, в этот момент вы присваиваете 0
переменной экземпляра @game_score
объекта класса Bowling
.
def hit(pins)
@game_score = @game_score + pins # (2)
Теперь (2) мы находимся внутри метода экземпляра класса Bowling
. I.e: Это метод, который будет принадлежать экземпляру Bowling
. Итак, теперь переменная экземпляра @game_score
принадлежит экземпляру класса Bowling
, а не самому классу.
Поскольку эта переменная экземпляра никогда не инициализируется ничем, она будет оцениваться как nil
(в Ruby неинициализированные переменные всегда оцениваются до nil
), поэтому это оценивается как @game_score = nil + pins
, а поскольку nil
не имеет a #+
, это приведет к возникновению исключения NoMethodError
.
end
def score
@game_score # (3)
И здесь (3) мы снова находимся внутри метода экземпляра класса Bowling
. Это всегда будет оцениваться как nil
по причине, описанной выше: @game_score
никогда не инициализируется, поэтому он оценивает nil
.
end
end
Мы можем использовать возможности отражения Ruby, чтобы посмотреть, что происходит:
p Bowling.instance_variable_get(:@game_score) # => 0
b = Bowling.new
p b.instance_variable_get(:@game_score) # => nil
Теперь добавьте значение в переменную экземпляра:
b.instance_variable_set(:@game_score, 1)
p b.score # => 1
b.hit(3)
p b.score # => 4
Итак, мы видим, что все работает так, как должно, нам нужно только выяснить, как убедиться, что переменная экземпляра инициализируется.
Для этого нам нужно написать метод инициализации. Как ни странно, метод инициализации фактически является методом частного экземпляра, который называется initialize
. (Причина, почему initialize
- это метод экземпляра, а не метод класса, на самом деле довольно прост. Ruby разбивает создание объекта в два этапа: выделение памяти и инициализация объекта. Распределение памяти выполняется методом класса alloc
и объектом инициализация выполняется методом экземпляра с именем initialize
. (Objective-C программисты это узнают.) Причина, по которой alloc
- это метод класса, просто состоит в том, что на данный момент выполнения пока нет экземпляра. что initialize
является методом экземпляра, заключается в том, что инициализация объекта, очевидно, выполняется для каждого объекта. Для удобства существует стандартный метод класса factory, называемый new
, который вызывает для вас как alloc
, так и initialize
.)
class Bowling
def initialize
@game_score = 0
end
end
Позвольте проверить это:
c = Bowling.new
p c.score # => 0
c.hit(2)
p c.score # => 2
BTW: только некоторые незначительные подсказки в стиле Ruby: отступы - это 2 пробела, а не 1 вкладка. И ваш метод hit
более идиоматически будет @game_score += pins
.
Поскольку у вас нет
def initialize
@game_score = 0
end
Назначение в определении класса не делает то, что, по вашему мнению, делает, и когда hit
вызывается, он не может добавить к nil
.
Если вы теперь спросите, что случилось с @game_score
?, ну, всегда помните, что Class является объектом, а Object - классом.
Так здорово, как классы Ruby имеют такое "реальное" существование в Zen. Ruby точно не имеет названных классов, скорее, имена классов являются ссылками на объекты класса Class
. Назначая @game_score
вне метода экземпляра, вы создали переменную экземпляра класса, атрибут объекта класса Bowling
, который является экземпляром класса Class
. Эти объекты, в общем, не очень полезны. (См. Главу 1 "Путь Руби", Хэл Фултон.)
@game_score
, определенная называется переменной класса экземпляра, которая является переменной, определенной для объекта singleton class:
class << Bowling
attr_accessor :game_score
end
Bowling.game_score #=> 0
Это, как вы можете отличить от обычных переменных экземпляра, определенных для объектов экземпляра.
@game_score никогда не получит здесь значение нуля - вам нужно его инициализировать внутри, как в
def инициализировать @game_score = 0 конец