People coming to Ruby from C++ often ask us, ``What happens to instance
variables in a mixin? In C++, I have to jump through some hoops to
control how variables are shared in a multiple-inheritance hierarchy.
How does Ruby handle this?''
Well, for starters, it's not really a fair question, we tell them.
Remember how instance variables work in Ruby: the first mention of an
``@''-prefixed variable creates the instance variable
in the
current object, self
.
For a mixin, this means that the module that you mix into your
client class (the mixee?) may create instance variables in the client
object and may use
attr
and friends to define accessors for
these instance variables. For instance:
module Notes
attr :concertA
def tuning(amt)
@concertA = 440.0 + amt
end
end
class Trumpet
include Notes
def initialize(tune)
tuning(tune)
puts "Instance method returns #{concertA}"
puts "Instance variable is #{@concertA}"
end
end
# The piano is a little flat, so we'll match it
Trumpet.new(-5.3)
|
produces:
Instance method returns 434.7
Instance variable is 434.7
|
Not only do we have access to the methods defined in the mixin, but we
get access to the necessary instance variables as well. There's a risk here, of
course, that different mixins may use an instance variable with
the same name and create a collision:
module MajorScales
def majorNum
@numNotes = 7 if @numNotes.nil?
@numNotes # Return 7
end
end
module PentatonicScales
def pentaNum
@numNotes = 5 if @numNotes.nil?
@numNotes # Return 5?
end
end
class ScaleDemo
include MajorScales
include PentatonicScales
def initialize
puts majorNum # Should be 7
puts pentaNum # Should be 5
end
end
ScaleDemo.new
|
produces:
The two bits of code that we mix in both use an instance
variable named
@numNotes
. Unfortunately, the result is probably
not what the author intended.
For the most part, mixin modules don't try to carry their own instance
data around---they use accessors to retrieve data from the client
object. But if you need to create a mixin that has to have its own
state, ensure that the instance variables have unique names to
distinguish them from any other mixins in the system (perhaps by using
the module's name as part of the variable name).