From: "Jesús Gabriel y Galán" Date: 2009-04-27T23:50:10+09:00 Subject: Re: KaraokeSong#to_s example in book On Mon, Apr 27, 2009 at 4:35 PM, Clifford Leung wrote: > 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.