KaraokeSong#to_s example in book

C

Clifford Leung

Hi,
I'm reading the online eBook of Programming Ruby, it's an excellent
resource for learning Ruby.
I am having some trouble understanding the KaraokeSong#to_s example.

Quoting the text:
"
class KaraokeSong
# ...
def to_s
"KS: #{@name}--#{@artist} (#{@duration}) [#{@lyrics}]"
end
end
aSong = KaraokeSong.new("My Way", "Sinatra", 225, "And now, the...")
aSong.to_s » "KS: My Way--Sinatra (225) [And now, the...]"

We're correctly displaying the value of the |@lyrics| instance variable.
To do this, the subclass directly accesses the instance variables of its
ancestors. So why is this a bad way to implement |to_s|? The answer has
to do with good programming style (and something called /decoupling/).
By poking around in our parent's internal state, we're tying ourselves
tightly to its implementation. Say we decided to change |Song| to store
the duration in milliseconds. Suddenly, |KaraokeSong| would start
reporting ridiculous values. The idea of a karaoke version of ``My Way''
that lasts for 3750 minutes is just too frightening to consider."

What does it mean by "the subclass directly accesses the instance
variables of its ancestors"? The Song class does not have the @lyrics
instance variable. And are @name, @artist, and @duration not instance
variables for the KaraokeSong class, so they have nothing to do with the
instance variables with the same name for the Song class? Even if I
change Song to store the duration in milliseconds, would not KaraokeSong
still store in minutes, because when I create a new KaraokeSong object,
I pass it the duration argument in minutes?

Perhaps an example of what could go wrong would help.

Dave Thomas kindly replied with the following:
"Classes don't have instance variables: instances do. But classes
contain the code that uses instance variables. So, in this case, the
code that "knows about" @name (for example) is in the Song class. The
subclass should not assume this internal implementation. Instead, it
should use the interface provided by the Song class, in this case using
its to_s method."

But I am still confused. OK, so the code that "knows about" @name, etc.
is in the Song class, but doesn't the KaraokeSong class also know about
these instance variables? Since it super'd the initialize method?

Thanks to clear this up.

Regards,
Clifford Leung
 
J

Jesús Gabriel y Galán

Hi,
I'm reading the online eBook of Programming Ruby, it's an excellent resou= rce
for learning Ruby.
I am having some trouble understanding the KaraokeSong#to_s example.

Quoting the text:
"
class KaraokeSong
=A0 =A0 =A0 =A0# ...
=A0 =A0 =A0 =A0def to_s
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0"KS: #{@name}--#{@artist} (#{@duration}) [= #{@lyrics}]"
=A0 =A0 =A0 =A0end
end
aSong =3D KaraokeSong.new("My Way", "Sinatra", 225, "And now, the...")
aSong.to_s =A0 =A0 =BB =A0 =A0 "KS: My Way--Sinatra (225) [And now, the..= ]"

We're correctly displaying the value of the |@lyrics| instance variable. = To
do this, the subclass directly accesses the instance variables of its
ancestors. So why is this a bad way to implement |to_s|? The answer has t= o
do with good programming style (and something called /decoupling/). By
poking around in our parent's internal state, we're tying ourselves tight= ly
to its implementation. Say we decided to change |Song| to store the durat= ion
in milliseconds. Suddenly, |KaraokeSong| would start reporting ridiculous
values. The idea of a karaoke version of ``My Way'' that lasts for 3750
minutes is just too frightening to consider."

What does it mean by "the subclass directly accesses the instance variabl= es
of its ancestors"? The Song class does not have the @lyrics instance
variable. And are @name, @artist, and @duration not instance variables fo= r
the KaraokeSong class, so they have nothing to do with the instance
variables with the same name for the Song class? Even if I change Song to
store the duration in milliseconds, would not KaraokeSong still store in
minutes, because when I create a new KaraokeSong object, I pass it the
duration argument in minutes?

Perhaps an example of what could go wrong would help.

Dave Thomas kindly replied with the following:
"Classes don't have instance variables: instances do. But classes contain
the code that uses instance variables. So, in this case, the code that
"knows about" @name (for example) is in the Song class. The subclass shou= ld
not assume this internal implementation. Instead, it should use the
interface provided by the Song class, in this case using its to_s method.= "

But I am still confused. OK, so the code that "knows about" @name, etc. i= s
in the Song class, but doesn't the KaraokeSong class also know about thes= e
instance variables? Since it super'd the initialize method?

No, because the public interface to create an instance of Song states
that you have to pass
the duration in minutes, but you don't know what calculations the Song
initialize method
might do to actually store the duration. An example:

class Song
def initialize name, artist, duration, lyrics
@name =3D name
@artist =3D artist
@duration =3D duration * 60000 # store the duration in ms
@lyrics =3D lyrics
end

def duration
@duration / 60000
end
end

if you blindly do:

class KaraokeSong < Song
def initialize name, artist, duration, lyrics
super
end

def to_s
"#{@name} - #{@artist} (#{@duration} minutes). #{@lyrics}"
end
end

Then you will be reporting wrong values for the duration. Instead you
should use the accessor that Song provides to access the duration in
minutes. The @duration is what the quoted authors say it's Song's
internal state, Song's internal implementation.
By calling super you are delegating the management of the duration to
Song's implementation, of which you shouldn't use its internal
structures for more decoupling.

Hope this clears it up a little bit.

Jesus.
 
C

Clifford Leung

Jesús Gabriel y Galán said:
Hi,
I'm reading the online eBook of Programming Ruby, it's an excellent resource
for learning Ruby.
I am having some trouble understanding the KaraokeSong#to_s example.

Quoting the text:
"
class KaraokeSong
# ...
def to_s
"KS: #{@name}--#{@artist} (#{@duration}) [#{@lyrics}]"
end
end
aSong = KaraokeSong.new("My Way", "Sinatra", 225, "And now, the...")
aSong.to_s » "KS: My Way--Sinatra (225) [And now, the..]"

We're correctly displaying the value of the |@lyrics| instance variable. To
do this, the subclass directly accesses the instance variables of its
ancestors. So why is this a bad way to implement |to_s|? The answer has to
do with good programming style (and something called /decoupling/). By
poking around in our parent's internal state, we're tying ourselves tightly
to its implementation. Say we decided to change |Song| to store the duration
in milliseconds. Suddenly, |KaraokeSong| would start reporting ridiculous
values. The idea of a karaoke version of ``My Way'' that lasts for 3750
minutes is just too frightening to consider."

What does it mean by "the subclass directly accesses the instance variables
of its ancestors"? The Song class does not have the @lyrics instance
variable. And are @name, @artist, and @duration not instance variables for
the KaraokeSong class, so they have nothing to do with the instance
variables with the same name for the Song class? Even if I change Song to
store the duration in milliseconds, would not KaraokeSong still store in
minutes, because when I create a new KaraokeSong object, I pass it the
duration argument in minutes?

Perhaps an example of what could go wrong would help.

Dave Thomas kindly replied with the following:
"Classes don't have instance variables: instances do. But classes contain
the code that uses instance variables. So, in this case, the code that
"knows about" @name (for example) is in the Song class. The subclass should
not assume this internal implementation. Instead, it should use the
interface provided by the Song class, in this case using its to_s method."

But I am still confused. OK, so the code that "knows about" @name, etc. is
in the Song class, but doesn't the KaraokeSong class also know about these
instance variables? Since it super'd the initialize method?

No, because the public interface to create an instance of Song states
that you have to pass
the duration in minutes, but you don't know what calculations the Song
initialize method
might do to actually store the duration. An example:

class Song
def initialize name, artist, duration, lyrics
@name = name
@artist = artist
@duration = duration * 60000 # store the duration in ms
@lyrics = lyrics
end

def duration
@duration / 60000
end
end

if you blindly do:

class KaraokeSong < Song
def initialize name, artist, duration, lyrics
super
end

def to_s
"#{@name} - #{@artist} (#{@duration} minutes). #{@lyrics}"
end
end

Then you will be reporting wrong values for the duration. Instead you
should use the accessor that Song provides to access the duration in
minutes. The @duration is what the quoted authors say it's Song's
internal state, Song's internal implementation.
By calling super you are delegating the management of the duration to
Song's implementation, of which you shouldn't use its internal
structures for more decoupling.

Hope this clears it up a little bit.

Jesus.

Hi,
Thanks for the reply.
I could understand if that was the case, but that's not the case in the
example in the book. For reference, I am reading:

http://www.ruby-doc.org/docs/ProgrammingRuby/html/tut_classes.html

If I understand the examples correctly, the example code they have used is:

class Song
def initialize(name, artist, duration)
@name = name
@artist = artist
@duration = duration
end

def to_s
"Song: #{@name}--#{@artist} (#{@duration})"
end
end

class KaraokeSong < Song
def initialize(name, artist, duration, lyrics)
super(name, artist, duration)
@lyrics = lyrics
end

def to_s
"KS: #{@name}--#{@artist} (#{@duration}) [#{@lyrics}]"
end
end

It says that you should not implement the class KaraokeSong's to_s
method as above, but rather, you should do it this way:

def to_s
super + " [#{@lyrics}]"
end

The reason being: "So why is this a bad way to implement to_s?

The answer has to do with good programming style (and something called
decoupling). By poking around in our parent's internal state, we're
tying ourselves tightly to its implementation. Say we decided to change
Song to store the duration in milliseconds. Suddenly, KaraokeSong would
start reporting ridiculous values. The idea of a karaoke version of ``My
Way'' that lasts for 3750 minutes is just too frightening to consider.

We get around this problem by having each class handle its own internal
state. When KaraokeSong#to_s is called, we'll have it call its parent's
to_s method to get the song details."

This is what I don't understand.

Thanks.

Cliff
 
J

Jesús Gabriel y Galán

I could understand if that was the case, but that's not the case in the
example in the book.

The point is that if you limit yourself to using the public interface
of a class,
you protect yourself against changes in the internal implementation.

Say that tomorrow, the implementer of the Song class receives a new requirement,
and he decides to store the duration in ms, to more easily implement the new
functionality. Then, if you have used the internal implementation, you
have a problem.

Hope this helps.

Jesus.
 
C

Clifford Leung

Jesús Gabriel y Galán said:
The point is that if you limit yourself to using the public interface
of a class,
you protect yourself against changes in the internal implementation.

Say that tomorrow, the implementer of the Song class receives a new requirement,
and he decides to store the duration in ms, to more easily implement the new
functionality. Then, if you have used the internal implementation, you
have a problem.

Hope this helps.

Jesus.

I think I've got it.

Thanks.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,065
Latest member
OrderGreenAcreCBD

Latest Threads

Top