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.
© something more elegant?

More elaborately (showing some of the key features)

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>

p = Picture.new(800) # Creates a new picture of 800pt width. p.add_track('first_track') p.add_track('second_track')

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.

On 11/13/06, jan aerts (RI) [email protected] wrote:

| | x x | |

be readable for its Track objects. This is not simple inheritance,
class Picture

some_xml_that_includes_the_width_which_was_defined_in_Picture
p = Picture.new(800) # Creates a new picture of 800pt width.
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

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

| | x x | |

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.

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

| | x x | |

   t = Track.new self, *a, &b
     @picture = picture
   def each_feature &b

etc.

Thanks. You really made that easy for me…

| | x x | |

 attr_accessor :name

end
variable, should I add width as an argument to
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 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.

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

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

| | x x | |

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.
© something more elegant?

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

Paul I. 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 V.

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs