Say I have the following trivial example:
// This is provided to me from another jar so cannot be changed.
public interface Shape {
double calculateArea();
double calculatePerimeter();
}
public class Triangle implements Shape {
private double side1;
private double side2;
private double side3;
public Triangle(double side1, double side2, double side3) {
this.side1 = side1;
this.side2 = side2;
this.side3 = side3;
}
@Override
public double calculateArea() {
// Heron's formula to calculate area of a triangle
double s = (side1 + side2 + side3) / 2;
return Math.sqrt(s * (s - side1) * (s - side2) * (s - side3));
}
@Override
public double calculatePerimeter() {
return side1 + side2 + side3;
}
}
public class Square implements Shape {
private double side;
public Square(double side) {
this.side = side;
}
@Override
public double calculateArea() {
return side * side;
}
@Override
public double calculatePerimeter() {
return 4 * side;
}
public void squareSpecificMethod(){
// Does something specific for squares only
}
}
public class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double calculateArea() {
return Math.PI * radius * radius;
}
@Override
public double calculatePerimeter() {
return 2 * Math.PI * radius;
}
}
public class Main {
public static void main(String[] args) {
// Example usage
Shape triangle = new Triangle(3, 4, 5);
System.out.println("Triangle Area: " + triangle.calculateArea());
System.out.println("Triangle Perimeter: " + triangle.calculatePerimeter());
Shape square = new Square(5);
System.out.println("Square Area: " + square.calculateArea());
System.out.println("Square Perimeter: " + square.calculatePerimeter());
Shape circle = new Circle(3);
System.out.println("Circle Area: " + circle.calculateArea());
System.out.println("Circle Perimeter: " + circle.calculatePerimeter());
}
}
If I want to access the method squareSpecificMethod()
I can do the following but would potentially have to do this multiple times in multiple places:
((Square) square).squareSpecificMethod();
or Likewise would have to do this in multiple places
if (square instanceof Square) {
((Square) square).squareSpecificMethod();
}
or change this to:
Shape square = new Square(5);
to
Square square = new Square(5);
Or use the visitor pattern but I cannot change the shape interface.
How can I avoid having to cast or use instanceOf as from what I've read this can be a potential code smell?
I'm struggling to see the best way of getting the concrete class at runtime when I may need to call implementation specific methods but may not know the implementation until runtime.
I would hit the same problem if I added specific methods to other implementations. And it would seem cumbersome to check instanceof or cast in multiple places
Constrained to Java 11
Keep it clean, if you need te test for subclasses and cast to subclasses, something is deeply wrong.
"Object orientation" is about making objects interact. Instead of to find out the subclass of object A and then calls some method specific to A, we create an an annex object that "knows more" by virtue of having been constructed together with A and calls A ("In Soviet Russia, object calls YOU" etc...)
Here we go. We use an annex interface
ShapeManglerto which we "give" theShape. The implementation of that annex interface "knows" how to handle theSquareextras. We pass the appropriate implementation of the annex interface together withShapeat the topmost call.As a side-bonus, we use it to eliminate code duplication made for the printing to
stdout-- by having it carry the name of the shape.We get: