Reputation: 49
['board'].each{|script| require_relative script}
class GameRunner
@board = Board.new
def initialize
end
def getBoard
@board
end
end
This piece of code generates an error when getBoard
is called. But when I move the instantiation of @board
to the initialize
block there is no errors. Why?
Edit: An answer with a more clear explanation on what is going on: Ruby class instance variable vs. class variable
Upvotes: 0
Views: 991
Reputation: 369594
Here @board
is an instance variable. Instance variables belong to an object (instance), hence why they are called instance variables.
You have two references to an instance variable named @board
in your code. Now, ask yourself: which object do they belong to? In other words: what is self
at the point where you reference the instance variable?
class GameRunner
# Here, `self` is `GameRunner`
@board = Board.new
def getBoard
# Here, `self` is an *instance* of `GameRunner`
@board
end
end
At the first reference to @board
, self
is the GameRunner
class itself. Remember, class are objects just like any other object; they are instances of the Class
class just like strings are instances of the String
class, integers are instances of the Integer
class, and game runners are instances of the GameRunner
class.
You can easily see that the instance variable has been defined and initialized:
GameRunner.instance_variables
#=> [:@board]
GameRunner.instance_variable_get(:@board)
#=> #<Board:0x0000deadbeef1230>
At your second mention, however, self
is an instance of GameRunner
, and not the GameRunner
class itself.
Or, to put it differently: you have two completely independent instance variables of two completely independent objects. The instance variables just happen to have the same name.
It's exactly the same as if you did:
game_runner1 = GameRunner.new
game_runner2 = GameRunner.new
The instance variables of game_runner1
and game_runner2
are private to each of those two objects. game_runner1
does not know anything about the instance variables of game_runner2
and vice versa. The same thing is true about game_runner1
and GameRunner
.
Again, it is important to remember that classes are just objects like any other object.
It looks like what you actually want is to have both references refer to the same instance variable, namely an instance variable of an instance of GameRunner
. You can achieve that by moving the assignment into an instance method, something like this:
class GameRunner
def initializeBoard
# Here, `self` is an *instance* of `GameRunner`
@board = Board.new
end
def getBoard
# Here, `self` is an *instance* of `GameRunner`
@board
end
end
However, this is somewhat annoying because you always have to remember to call initializeBoard
before you can use the object, and you have to make sure that once you have called initializeBoard
, you never call it again.
To make initialization tasks like this easier, Ruby has a convention: the default implementation of Class#new
will call a method named initialize
on the newly allocated object:
class Class
def new(...)
obj = allocate
obj.initialize(...)
obj
end
end
[This is not quite accurate because initialize
is private by default, so it would be more like obj.__send__(:initialize, ...)
, but you get the idea.]
So, if we simply rename the initializeBoard
method to initialize
, that will ensure that our instance variable is always initialized by GameRunner::new
:
class GameRunner
def initialize
# Here, `self` is an *instance* of `GameRunner`
@board = Board.new
end
def getBoard
# Here, `self` is an *instance* of `GameRunner`
@board
end
end
Note that your code violates multiple Ruby community coding standards:
class
or before end
snake_case
, not camelCase
. IOW, your getter method should be called get_board
.noun
, not get_noun
, i.e. your getter method should be called simply board
.Module#attr_reader
method.If we combine all of this, your class should look like this:
class GameRunner
attr_reader :board
def initialize
@board = Board.new
end
end
I, personally, prefer to avoid referring to instance variables directly as much as possible, and only use getters and setters. However, that is not a majority coding style, that is just my personal preference:
class GameRunner
attr_reader :board
private
def initialize
self.board = Board.new
end
attr_writer :board
end
Upvotes: 1
Reputation: 211720
Here @board
is a class variable, not an instance variable.
What you probably mean is:
class GameRunner
# Anything declared here is assumed to be class-level
def initialize
# Anything inside an instance method is an instance variable
@board = Board.new
end
def getBoard
@board
end
end
Since classes are objects, the class can also have its own instance variables. Confusingly they also use the same @
prefix.
It's worth noting that accessors like this are usually declared in Ruby as:
attr_reader :board
Which makes the method for you. The get
prefix is almost always omitted because mutator methods (e.g. set
) are the same but with the =
suffix.
Upvotes: 0