Total newbie: Instance variables in ruby?

后端 未结 4 1312
旧巷少年郎
旧巷少年郎 2020-12-01 11:39

Pardon the total newbiew question but why is @game_score always nil?

#bowling.rb

class Bowling
  @game_score = 0
    def hit(pins)
        @game_score = @ga         


        
相关标签:
4条回答
  • 2020-12-01 11:45

    Let's walk through the code, shall we?

    #bowling.rb
    
    class Bowling
      @game_score = 0 # (1)
    

    At this point (1), we are still inside the class Bowling. Remember: classes are just objects like any other. So, at this point you are assigning 0 to the instance variable @game_score of the class object Bowling.

     def hit(pins)
      @game_score = @game_score + pins # (2)
    

    Now (2), we are inside an instance method of the Bowling class. I.e.: this is a method that is going to belong to an instance of Bowling. So, now the instance variable @game_score belongs to an instance of the Bowling class, and not to the class itself.

    Since this instance variable is never initialized to anything, it will evaluate to nil (in Ruby, uninitialized variables always evaluate to nil), so this evaluates to @game_score = nil + pins and since nil doesn't have a #+ method, this will result in a NoMethodError exception being raised.

     end
     def score
      @game_score # (3)
    

    And here (3), we are again inside an instance method of the Bowling class. This will always evaluate to nil, for the reason I outlined above: @game_score is never initialized, therefore it evaluates to nil.

     end
    end
    

    We can use Ruby's reflection capabilities to take a look at what's going on:

    p Bowling.instance_variable_get(:@game_score) # => 0
    b = Bowling.new
    p b.instance_variable_get(:@game_score) # => nil
    

    Now let's inject a value into the instance variable:

    b.instance_variable_set(:@game_score, 1)
    p b.score # => 1
    b.hit(3)
    p b.score # => 4
    

    So, we see that everything works as it should, we only need to figure out how to make sure the instance variable gets initialized.

    To do that, we need to write an initializer method. Strangely, the initializer method is actually a private instance method called initialize. (The reason why initialize is an instance method and not a class method, is actually quite simple. Ruby splits object creation in two phases: memory allocation and object initialization. Memory allocation is done by a class method called alloc and object initialization is done by an instance method called initialize. (Objective-C programmers will recognize this.) The reason why alloc is a class method is simply that at this point in the execution there is no instance yet. And the reason that initialize is an instance method is that object initialization is obviously per-object. As a convenience, there is a standard factory class method called new that calls both alloc and initialize for you.)

    class Bowling
     def initialize
      @game_score = 0
     end
    end
    

    Let's test this:

    c = Bowling.new
    p c.score # => 0
    c.hit(2)
    p c.score # => 2
    

    BTW: just some minor Ruby style tips: indentation is 2 spaces, not 1 tab. And your hit method would more idiomatically be @game_score += pins.

    0 讨论(0)
  • 2020-12-01 11:51

    Because you don't have

    def initialize
      @game_score = 0
    end
    

    The assignment in the class definition is not doing what you think it is doing, and when hit gets invoked it can't add to nil.

    If you now ask what happened to @game_score?, well, always remember Class is an object and Object is a class.

    It's way cool the way Ruby classes have this Zen-like "real" existence. Ruby doesn't precisely have named classes, rather, class names are references to objects of class Class. By assigning to @game_score outside of an instance method you created a class instance variable, an attribute of the class object Bowling, which is an instance of class Class. These objects are not, in general, very useful. (See Chapter 1, The Ruby Way, Hal Fulton.)

    0 讨论(0)
  • 2020-12-01 11:54

    @game_score will never get a value of zero here - you need to put it inside initialize, as in

    def initialize @game_score = 0 end

    0 讨论(0)
  • 2020-12-01 12:02

    @game_score defined there is called class instance variable, which is a variable defined for the singleton class object:

    class << Bowling
      attr_accessor :game_score
    end
    
    Bowling.game_score #=> 0
    

    This is as you can tell different from the normal instance variables defined for instance objects.

    0 讨论(0)
提交回复
热议问题