views:

52

answers:

2

Hi,

Imagine the following scenario.

class Shape
{
    // methods of Class Shape
}

class Circle extends Shape
{
   // methods of Class Shape
   // methods of Class Circle 
}

class Square extends Shape
{
  // methods of Class Shape
  // methods of Class Square 
}

class Canvas  // This class should a bucket of objects that belong to Square and Circle 
{
  // Declaring a single list of supertype
  List<Shape> heterogeneousCollection;

  // Declaring two separate lists
  List<Cirlce> homogeneousCollection;
  List<Square> homogeneousCollection; 
}

The corresponding relational schema is as follows

  • Table Shape contains master information
  • Tables Circle & Square enrich data in Shape by a way of foreign key reference (Circle & - Square are disjoint sets with different columns)
  • Table Canvas is a sort of aggregation of data
  • Canvas joins Circle (1..n cardinality)
  • Canvas joins Square (1..n cardinality)

Which is the better approach "Declaring a single list of supertype (heterogeneous collection)" or "Declaring two separate lists (two different homogeneous collections)"

I'm considering the following points when making a decision.

  1. How do we populate object Canvas when reading from a database?

Consider a way of avoiding N+1 problem and reading from a single query something like

SELECT * FROM 
Canvas INNER JOIN Circle ON ..  
Canvas INNER JOIN Square ON ..  
Circle INNER JOIN Shape ON ..  
Square INNER JOIN Shape ON ..   

Now, for every one record of Canvas we end up with (B+C) rows. However, with several ORM tools it is possible of group distinct data sets in Circle and Square into two separate lists. (I'm considering iBatis here)

  1. How do we handle several functions on these objects?
    Imaging a case where we are planning to handle a UI function to display data in object Canvas. Apart from common functions between Circle and Square, each of them can have different functions. For example Square can have getLengthOfSide(), whereas Circle can have getRadius(). If we use heterogeneous list, we might end up using a cast operator at every place we need access to these functions.

    Class Canvas  
    {  
        void drawAll()  
        {  
            for (Shape s : heterogeneousCollection)  
            {  
                if (s instanceof Circle)  
                {  
                    s.draw(); // common function present in Shape also  
                    s.xyz(); //  specific to Circle  
                }  
                if (s instanceof Square)  
                {  
                    s.draw(); // common function present in Shape also  
                    s.abc(); //  specific to Square  
                }  
            }   
        }  
    }  
    

In case of two homogeneous list we might have two different for loops for each list separately.

However, if we need to add a new kind of shape (say triangle), it is affecting Canvas, which I feel is a result of design flaw and Java might be equipped to deal with this. Kindly throw some light on this. Any reference to books/links would be of great help. Just wanted to tell you guys that this is not a school assignment and I'm seriously looking at various solutions. Pardon me for a long question.

PS: One other solution List<? extends Shape> is ruled out because we cannot insert any objects into this collection.

A: 

It's difficult to give solutions when we have general information instead of specific facts. Case in point, the xyz() and abc() methods. What are they, and why? In your example, they seem to have a similar use, so I'd consider defining an abstract method in Shape called doSomethingSpecific() so it would be implemented by all Shape subclasses. Now your code is:

   void drawAll()  
       {  
       for (Shape s : heterogeneousCollection)  
           {
           s.draw();                // common function present in Shape also  
           s.doSomethingSpecific(); //  specific to each implementation  
           }   
       }  

I prefer heterogeneous collections when possible. I very much dislike instanceof for the very reason you point out - what happens when we add Triangle to the mix.

Tony Ennis
Thanks a lot for your response.
Gopal
A: 

Assuming that there are a number of places where you legitimately need to deal with processing Circle and Square independently, I would deal with the heterogeneous collection by applying the Visitor Pattern

This has the benefit that if you add Triangle later, then when you add a method T visitTriangle(Triangle triangle); to the visitor, your code won't compile until you update every single visitor, avoiding nasty runtime surprises.

However... if you're really only talking about a single instance of processing Circle and Square differently, then applying Visitor here is overkill and I would just consider adding an abstract method to Shape for doSomeSpecificUiThing().

It would look something like this:

class ShapeVisitor<T>
{
    T visitCircle(Circle circle);
    T visitSquare(Square square);
}

class Shape
{
    abstract <T> T accept(ShapeVisitor<T> visitor);

    // methods of Class Shape
}

class Circle extends Shape
{
   <T> T accept(ShapeVisitor<T> visitor) {
       return visitor.visitCircle(this);
   }

   // methods of Class Circle 
}

class Square extends Shape
{
   <T> T accept(ShapeVisitor<T> visitor) {
       return visitor.visitSquare(this);
   }

  // methods of Class Square 
}

Class Canvas  
{  
    void drawAll()  
    {  
        for (Shape s : heterogeneousCollection)  
        {  
            s.draw();
            s.accept(new ShapeVisitor<Void>() {
                @Override Void visitCircle(Circle circle) {
                    circle.xyz();
                    return null;
                }

                @Override Void visitSquare(Square square) {
                    square.abc();
                    return null;
                }
            }
        }   
    }  
}  
Michael D
Thanks a lot for your response. I will try to read more about visitor patter.
Gopal
Ok now that I got nice answer, I would like to share that iBatis does support inheritance mapping. This is possible using "discriminator" and "subtype" attributes of a resultMap. A close read at documentation can be of more help.
Gopal