In general, when you're iterating over the items of a heterogeneous collection (in this case, containing both Rectangles and Circles), you have two choices:
- Only deal with methods available on the parent type (here,
Shape).
- Use
instanceof checks to handle each subtype differently.
Design A: Put all the functionality in Shape
With this design, every Shape is reponsible for knowing how to print its own attributes. This uses choice 1 - the looping code never needs to know whether it has a Rectangle or a Circle.
To Shape, you'd add
abstract String getAttributesString();
Rectangle implements this as
@Override
String getAttributesString() {
return String.format("Rectangle {b=%f, h=%f}", b, h);
}
Then the loop is
for (Shape shape : shapes) {
System.out.println(shape.getAttributesString());
}
You could also override the toString() method on Object, but it's usually best to only use toString for debugging, rather than for something that, for example, needs to be shown to a user. Classes should generally have a toString override that prints a full representation of the instance's data.
Design B: Instanceof checks
This is choice 2. The change is only to the loop code - It does not require modifying the shapes at all.
for (Shape shape : shapes) {
if (shape instanceof Rectangle) {
Rectangle rectangle = (Rectangle) shape;
System.out.println(String.format("Rectangle {b=%f, h=%f}", rectangle.getB(), rectangle.getH()));
} else if (shape instanceof Circle) {
Circle circle = (Circle) shape;
System.out.println(String.format("Circle {r=%f}", circle.getR()));
}
}
Design C: Visitor pattern
This is sort of a mix of the two choices.
interface ShapeVisitor {
void visitRectangle(Rectangle rectangle);
void visitCircle(Circle circle);
}
abstract class Shape {
void visit(ShapeVisitor visitor);
/* ... the rest of your code ... */
}
class Rectangle extends Shape {
@Override
void visit(ShapeVisitor visitor) {
visitor.visitRectangle(this);
}
/* ... the rest of your code ... */
}
class Circle extends Shape {
@Override
void visit(ShapeVisitor visitor) {
visitor.visitCircle(this);
}
/* ... the rest of your code ... */
}
Then the loop looks like:
for (Shape shape : shapes) {
shape.visit(new ShapeVisitor() {
@Override
void visitRectangle(Rectangle rectangle) {
System.out.println(String.format("Rectangle {b=%f, h=%f}", rectangle.getB(), rectangle.getH()));
}
@Override
void visitCircle(Circle circle) {
System.out.println(String.format("Circle {r=%f}", circle.getR()));
}
});
}
Which to use?
Design A was nice because it avoids instanceof and casting, but the drawback was that you had to put the printing logic in the shape classes themselves, which takes away some flexibility (what if you wanted to print the same list of shapes differently in two different situations?).
Design B put the printing logic where you wanted it, but with casting you don't get to take full advantage of the compiler's type checking. It's error-prone because if you were to, for instance, add another Shape subtype, you could forget to update the loop code.
Design C sort of combines the best features of A and B. The problem, though, is that it isn't extensible. If you're writing a library that other people are going to use to define their own Shape subtypes, they can't do that because it would require modifying the ShapeVisitor interface. But it is suitable if your code doesn't need to be extensible in that way.
Or...
Ultimately, all of this is way easier in Scala. (yes, I realize I am no longer answering your question, and just having fun now.)
sealed trait Shape {
def color: String
def area: Double
}
case class Rectangle(b: Double, h: Double, color: String) extends Shape {
def area: Double = b*h
}
case class Circle(r: Double, color: String) extends Shape {
def area: Double = math.Pi*r*r
}
for (shape <- shapes) {
println(shape match {
case rectangle: Rectangle =>
"Rectangle {b=%f, h=%f}".format(rectangle.b, rectangle.h)
case circle: Circle =>
"Circle {r=%f}".format(circle.r)
})
}
Area()method (which you should rename toarea()by the way, in order to stick to naming conventions in Java).getAreaorcalculateArea. As far as I know, area is not a verb.