Classes, Objects, and Variables
From the examples we've shown so far, you might be wondering about our
earlier assertion that Ruby is an object-oriented language. Well,
this chapter is where we justify that claim. We're going to be looking
at how you create classes and objects in Ruby, and at some of the ways
in which Ruby is more powerful than most object-oriented languages.
Along the way, we'll be implementing part of our next billion-dollar
product, the Internet Enabled Jazz and Blue Grass jukebox.
After months of work, our highly paid Research and Development folks
have determined that our jukebox needs
songs. So it seems like
a good idea to start off by setting up a Ruby class that represents
things that are songs. We know that a real song has a name, an artist, and
a duration, so we'll want to make sure that the song objects in our
program do, too.
We'll start off by creating a basic class
Song
,
[As we
mentioned on page 9, class names start with an
uppercase letter, while method names start with a lowercase letter.]
which contains just a single method,
initialize
.
class Song
def initialize(name, artist, duration)
@name = name
@artist = artist
@duration = duration
end
end
|
initialize
is a special method in Ruby programs. When you
call
Song.new
to create a new
Song
object, Ruby creates an
uninitialized object and then calls that object's
initialize
method, passing in any parameters that were passed to
new
. This gives you a chance to write code that sets up your
object's state.
For class
Song
, the
initialize
method takes three
parameters. These parameters act just like local variables within the
method, so they follow the local variable naming convention of
starting with a lowercase letter.
Each object represents its own song, so we need each of our
Song
objects to carry around its own song name, artist, and duration. This
means we need to store these values as
instance variables
within the object.
In Ruby, an instance variable is simply a name
preceded by an ``at'' sign (``@''). In our example, the parameter
name
is assigned to the instance variable
@name
,
artist
is assigned to
@artist
, and
duration
(the
length of the song in seconds) is assigned to
@duration
.
Let's test our spiffy new class.
aSong = Song.new("Bicylops", "Fleck", 260)
|
aSong.inspect
|
� |
"#<Song:0x401b4924 @duration=260, @artist=\"Fleck\", @name=\"Bicylops\">"
|
Well, it seems to work. By default, the
inspect
message,
which can be sent to any object, dumps out the object's id and instance
variables. It looks as though we have them set up correctly.
Our experience tells us that during development we'll be printing out
the contents of a
Song
object many times, and
inspect
's
default formatting leaves something to be desired. Fortunately, Ruby
has a standard message,
to_s
,
which it
sends to any object it wants to render as a string. Let's try it on
our song.
aSong = Song.new("Bicylops", "Fleck", 260)
|
aSong.to_s
|
� |
"#<Song:0x401b499c>"
|
That wasn't too useful---it just reported the object id. So, let's
override
to_s
in our class.
As we do this, we should also take a moment to talk about how we're
showing the class definitions in this book.
In Ruby, classes are never closed: you can always add methods to an
existing class.
This applies to the classes you write as well as the
standard, built-in classes. All you have to do is open up a class
definition for an existing class, and the new contents you specify
will be added to whatever's there.
This is great for our purposes. As we go through this chapter, adding
features to our classes, we'll show just the class definitions for the
new methods; the old ones will still be there. It saves us having to
repeat redundant stuff in each example. Obviously, though, if you were
creating this code from scratch, you'd probably just throw all the
methods into a single class definition.
Enough detail! Let's get back to adding a
to_s
method to our
Song
class.
class Song
|
def to_s
|
"Song: #{@name}--#{@artist} (#{@duration})"
|
end
|
end
|
aSong = Song.new("Bicylops", "Fleck", 260)
|
aSong.to_s
|
� |
"Song: Bicylops--Fleck (260)"
|
Excellent, we're making progress. However, we've slipped in something
subtle. We said that Ruby supports
to_s
for all objects, but
we didn't say how. The answer has to do with inheritance, subclassing,
and how Ruby determines what method to run when you send a message to
an object. This is a subject for a new section, so....