Classes within classes

Hi all,

I'm trying to create a little library for drawing my data using SVG. One
of the inherent properties of the drawings that I have to make, is that
each picture can contain one or more tracks with each track containing
one or more features (see ASCII art below).

+----------------------------picture-+

···

+----------------------track1----+ |

> > > >
> > x x | |
> > x x | |
> > > >
> > > >
> +--------------------------------+ |
> >
> +----------------------track2----+ |
> > > >
> > xx | |
> +--------------------------------+ |
+------------------------------------+

As a track can _only_ be defined within a picture, and a feature can
_only_ be defined within a track, I have set up the classes as follows:
class Picture
   class Track
     class Feature
     end
   end
end

The problem is that some of the properties of a Picture object have to
be readable for its Track objects. This is _not_ simple inheritance,
because Picture and Picture::Track are two completely different things.
As I see it, there are several options:
(A) either pass those properties as arguments every time you create a
new Picture::Track object. Not optimal, because the same piece of data
is copied over and over again.
(B) set those features as global variables. Not optimal as well.
(C) something more elegant?

More elaborately (showing some of the key features)
<LIB CODE SNIPPET>
class Picture
   def initialize(width)
     @width = width
     @tracks = Array.new
   end
   attr_accessor :width, :tracks

   def add_track(name)
     @tracks.push(Picture::Track.new(name)
     return @tracks[-1]
   end

   class Track
     # DO NOT CALL INITIALIZE METHOD DIRECTLY: use Picture#add_track
     def initialize(name)
       @name = name
     end
     attr_accessor :name

     def to_svg
       # I NEED THE WIDTH OF THE PICTURE HERE
       return
some_xml_that_includes_the_width_which_was_defined_in_Picture
     end

     class Feature
       ...
     end
   end
end
</LIB CODE SNIPPET>

<USAGE SNIPPET>
p = Picture.new(800) # Creates a new picture of 800pt width.
p.add_track('first_track')
p.add_track('second_track')
</USAGE SNIPPET>

In the end, the question is: should I make Picture#width a global
variable, should I add width as an argument to
Picture::Track#initialize, or is there a more elegant solution? (I hope
it's the last one...)

Any help would be very much appreciated,
jan.

Dr Jan Aerts
Bioinformatics Group
Roslin Institute
Roslin, Scotland, UK
+44 131 527 4200

---------The obligatory disclaimer--------
The information contained in this e-mail (including any attachments) is
confidential and is intended for the use of the addressee only. The
opinions expressed within this e-mail (including any attachments) are
the opinions of the sender and do not necessarily constitute those of
Roslin Institute (Edinburgh) ("the Institute") unless specifically
stated by a sender who is duly authorised to do so on behalf of the
Institute.

Hi,

two ideas:
1. pass the picture (it)self to the Track constructor and then query
whatever you need
2. have all the svg generating stuff in another class that will be
given the Picture object, and will iterate through it, creating the
xml along the way (ERB template comes to my mind...)

Jan

···

On 11/13/06, jan aerts (RI) <jan.aerts@bbsrc.ac.uk> wrote:

Hi all,

I'm trying to create a little library for drawing my data using SVG. One
of the inherent properties of the drawings that I have to make, is that
each picture can contain one or more tracks with each track containing
one or more features (see ASCII art below).

+----------------------------picture-+
> +----------------------track1----+ |
> > > >
> > x x | |
> > x x | |
> > > >
> +--------------------------------+ |
> >
> +----------------------track2----+ |
> > > >
> > xx | |
> +--------------------------------+ |
+------------------------------------+

As a track can _only_ be defined within a picture, and a feature can
_only_ be defined within a track, I have set up the classes as follows:
class Picture
   class Track
     class Feature
     end
   end
end

The problem is that some of the properties of a Picture object have to
be readable for its Track objects. This is _not_ simple inheritance,
because Picture and Picture::Track are two completely different things.
As I see it, there are several options:
(A) either pass those properties as arguments every time you create a
new Picture::Track object. Not optimal, because the same piece of data
is copied over and over again.
(B) set those features as global variables. Not optimal as well.
(C) something more elegant?

More elaborately (showing some of the key features)
<LIB CODE SNIPPET>
class Picture
   def initialize(width)
     @width = width
     @tracks = Array.new
   end
   attr_accessor :width, :tracks

   def add_track(name)
     @tracks.push(Picture::Track.new(name)
     return @tracks[-1]
   end

   class Track
     # DO NOT CALL INITIALIZE METHOD DIRECTLY: use Picture#add_track
     def initialize(name)
       @name = name
     end
     attr_accessor :name

     def to_svg
       # I NEED THE WIDTH OF THE PICTURE HERE
       return
some_xml_that_includes_the_width_which_was_defined_in_Picture
     end

     class Feature
       ...
     end
   end
end
</LIB CODE SNIPPET>

<USAGE SNIPPET>
p = Picture.new(800) # Creates a new picture of 800pt width.
p.add_track('first_track')
p.add_track('second_track')
</USAGE SNIPPET>

In the end, the question is: should I make Picture#width a global
variable, should I add width as an argument to
Picture::Track#initialize, or is there a more elegant solution? (I hope
it's the last one...)

Any help would be very much appreciated,
jan.

this is not true - ruby __always__ passes references (unless you are
seializing an object over a wire or to file) so there's no harm in having a
reference to a 'parent' object. in fact, given your spec, with all the
'_only_' clauses, it makes perfect sense to design the nested classes as a
factory chain, eg

   class Picture
     def new_track *a, &b
       Track.new self, *a, &b
     end

     class Track
       def initialize picture, *a, &b
         @picture = picture
       end

       def new_feature *a, &b
         Feature.new self, *a, &b
       end

       class Feature
         def initialize track, *a, &b
           @track = track
         end
       end
     end
   end

so

   picture = Picture.new

   track = picture.new_track :foo, :bar

   feature = track.new_feature :bar, :foo

this also has the nice feature that children prevent any required parents from
having the gc evaporate them - so long as we have a reference up the object
will not disappear. in fact, i'd probably setup the association as a two-way:

   class Picture
     Tracks =

     def new_track *a, &b
       t = Track.new self, *a, &b
     ensure
       Tracks << t
     end

     def each_track &b
       Tracks.each &b
     end

     class Track
       def initialize picture, *a, &b
         @picture = picture
       end

       Features =

       def new_feature *a, &b
         f = Feature.new self, *a, &b
       ensure
         Features << f
       end

       def each_feature &b
         Features.each &b
       end

       class Feature
         def initialize track, *a, &b
           @track = track
         end
       end
     end
   end

etc.

-a

···

On Mon, 13 Nov 2006, jan aerts (RI) wrote:

Hi all,

I'm trying to create a little library for drawing my data using SVG. One
of the inherent properties of the drawings that I have to make, is that
each picture can contain one or more tracks with each track containing
one or more features (see ASCII art below).

+----------------------------picture-+
> +----------------------track1----+ |
> > > >
> > x x | |
> > x x | |
> > > >
> +--------------------------------+ |
> >
> +----------------------track2----+ |
> > > >
> > xx | |
> +--------------------------------+ |
+------------------------------------+

As a track can _only_ be defined within a picture, and a feature can
_only_ be defined within a track, I have set up the classes as follows:
class Picture
  class Track
    class Feature
    end
  end
end

The problem is that some of the properties of a Picture object have to
be readable for its Track objects. This is _not_ simple inheritance,
because Picture and Picture::Track are two completely different things.
As I see it, there are several options:
(A) either pass those properties as arguments every time you create a
new Picture::Track object. Not optimal, because the same piece of data
is copied over and over again.

--
my religion is very simple. my religion is kindness. -- the dalai lama

Why don't you just add a member "container" or "parent" to the Feature (and maybe also Track) so a Track knows all of its Features and the Features all know the Track that they are part of? That's a common idiom for tree storage if you need to navigate both directions (from root to leaf and vice versa).

Another note: while I can see why you nest classes that might become a bit unreadable. Also it does not prevent any outside code from accessing class Track and Feature - basically just the names get more complex through the nesting. An alternative approach is to just define a module as namespace and nest all classes in it.

module PictureApp
   class Picture
   end

   class Track
   end

   class Feature
   end
end

Kind regards

  robert

···

On 13.11.2006 15:50, jan aerts (RI) wrote:

Hi all,

I'm trying to create a little library for drawing my data using SVG. One
of the inherent properties of the drawings that I have to make, is that
each picture can contain one or more tracks with each track containing
one or more features (see ASCII art below).

+----------------------------picture-+
> +----------------------track1----+ |
> > > >
> > x x | |
> > x x | |
> > > >
> +--------------------------------+ |
> >
> +----------------------track2----+ |
> > > >
> > xx | |
> +--------------------------------+ |
+------------------------------------+

As a track can _only_ be defined within a picture, and a feature can
_only_ be defined within a track, I have set up the classes as follows:
class Picture
   class Track
     class Feature
     end
   end
end

The problem is that some of the properties of a Picture object have to
be readable for its Track objects. This is _not_ simple inheritance,
because Picture and Picture::Track are two completely different things.
As I see it, there are several options:
(A) either pass those properties as arguments every time you create a
new Picture::Track object. Not optimal, because the same piece of data
is copied over and over again.
(B) set those features as global variables. Not optimal as well.
(C) something more elegant?

Thanks for your swift reply, Jan.

I did look briefly at these two options, but didn't go for them
previously. However, passing the Picture object itself _might_ actually
be the best solution. I suppose the code would then look something like:

class Picture
  def add_track(self, name)
    @tracks.push(Picture::Track.new(self, name))
  end

  class Track
    def initialize(picture, name)
      width = picture.width
      name = name
    end
  end
end

I'll probably go for this one...

jan.

···

-----Original Message-----
From: Jan Svitok [mailto:jan.svitok@gmail.com]
Sent: 13 November 2006 15:03
To: ruby-talk ML
Subject: Re: classes within classes

>
> +----------------------------picture-+
> > +----------------------track1----+ |
> > > > >
> > > x x | |
> > > x x | |
> > > > >
> > > > >
> > +--------------------------------+ |
> > >
> > +----------------------track2----+ |
> > > > >
> > > xx | |
> > +--------------------------------+ |
> +------------------------------------+
>
> <SNIP>
>
> More elaborately (showing some of the key features) <LIB
CODE SNIPPET>
> class Picture
> def initialize(width)
> @width = width
> @tracks = Array.new
> end
> attr_accessor :width, :tracks
>
> def add_track(name)
> @tracks.push(Picture::Track.new(name)
> return @tracks[-1]
> end
>
> class Track
> # DO NOT CALL INITIALIZE METHOD DIRECTLY: use Picture#add_track
> def initialize(name)
> @name = name
> end
> attr_accessor :name
>
> def to_svg
> # I NEED THE WIDTH OF THE PICTURE HERE
> return
> some_xml_that_includes_the_width_which_was_defined_in_Picture
> end
>
> class Feature
> ...
> end
> end
> end
> </LIB CODE SNIPPET>
>
> <USAGE SNIPPET>
> p = Picture.new(800) # Creates a new picture of 800pt width.
> p.add_track('first_track')
> p.add_track('second_track')
> </USAGE SNIPPET>
>
> In the end, the question is: should I make Picture#width a global
> variable, should I add width as an argument to
> Picture::Track#initialize, or is there a more elegant solution? (I
> hope it's the last one...)
>
> Any help would be very much appreciated, jan.

Hi,

two ideas:
1. pass the picture (it)self to the Track constructor and
then query whatever you need 2. have all the svg generating
stuff in another class that will be given the Picture object,
and will iterate through it, creating the xml along the way
(ERB template comes to my mind...)

Jan

Thanks. You really made that easy for me...

···

-----Original Message-----
From: ara.t.howard@noaa.gov [mailto:ara.t.howard@noaa.gov]
Sent: 13 November 2006 15:38
To: ruby-talk ML
Subject: Re: classes within classes

>
> +----------------------------picture-+
> > +----------------------track1----+ |
> > > > >
> > > x x | |
> > > x x | |
> > > > >
> > > > >
> > +--------------------------------+ |
> > >
> > +----------------------track2----+ |
> > > > >
> > > xx | |
> > +--------------------------------+ |
> +------------------------------------+
>
> As a track can _only_ be defined within a picture, and a
feature can
> _only_ be defined within a track, I have set up the classes
as follows:
> class Picture
> class Track
> class Feature
> end
> end
> end
>
this also has the nice feature that children prevent any
required parents from having the gc evaporate them - so long
as we have a reference up the object will not disappear. in
fact, i'd probably setup the association as a two-way:

   class Picture
     Tracks =

     def new_track *a, &b
       t = Track.new self, *a, &b
     ensure
       Tracks << t
     end

     def each_track &b
       Tracks.each &b
     end

     class Track
       def initialize picture, *a, &b
         @picture = picture
       end

       Features =

       def new_feature *a, &b
         f = Feature.new self, *a, &b
       ensure
         Features << f
       end

       def each_feature &b
         Features.each &b
       end

       class Feature
         def initialize track, *a, &b
           @track = track
         end
       end
     end
   end

etc.

why can't you just use inheritage and mixins? would that generate a worse
design then what you currently have?

Paul Irofti wrote:

why can't you just use inheritage and mixins? would that generate a worse
design then what you currently have?

http://www.parashift.com/c++-faq-lite/multiple-inheritance.html#faq-25.4
- notably the comments in Rule #1

While a C++ text, the idea is relevant - C++ multiple inheritance was
used to implement mixins (Rule #2 in that article encourages that), and
the same rules should apply to Ruby as well.

In fact, most of the uses of normal inheritance I see in Ruby code is to
save you (a little) typing over composition, instead of overriding /
implementing the parent's methods. The latter is usually unnecessary
because of dynamic typing and blocks.

David Vallner