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
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.
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)
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.
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;
}
}
}
}
}