The phenomenon is called as polymorphism through inheritance. That means your behavior is decided at runtime on which object is being called instead of by which reference it is being called.
Well. lets extend your example further. Lets first create class hierarchy
class Shape{
public void draw(){}
}
class Circle extends Shape{
public void draw(){
//mechanism to draw circle
}
}
class Square extends Shape{
public void draw(){
//mechanism to draw square
}
}
Now let's see how this can lead to clean code
class Canvas{
public static void main(String[] args){
List<Shape> shapes = new ArrayList<>();
shapes.add(new Circle());
shapes.add(new Square());
// clean and neat code
for(Shape shape : shapes){
shape.draw();
}
}
}
This can also help in making loose coupled system
Class ShapeDrawer{
private Shape shape;
public void setShape(Shape shape){
this.shape = shape;
}
public void paint(){
shape.draw();
}
}
In this case, ShapeDrawer
is very loosely coupled with actual shape. ShapeDrawer
even doesn't know which type of Shape
it is drawing or even mechanism of how it is drawing is abstracted from it. Underlying mechanism of drawing particular shape can be changed without affecting this class.