Dynamic Method Dispatch and Polymorphism

NamyaLG
5 min readJul 5, 2023

Polymorphism is a key feature of Object Oriented Programming. Simply put, it allows different forms or multiple behaviors of a particular method. The behavior exhibited is determined by the invoking object.

Derived classes can choose to override the methods of the parent class, in Java, this is indicated by the annotation @Override. In the case of abstract classes, i.e. classes where at least one function has no definition or interfaces, where the derived class is mandated to define and implement the function(s).

Consider the example of various athletic events such as sprints, jumps, throws, etc that happen in an athletic meet, it can be modeled as:

UML Class diagram
package Events; 

public class Main {
public static void main(String args[]){
AthleticEvents hundredMSprint = new Track("Sprint", 100);
AthleticEvents halfMarathon = new Track("Long Distance", 21000);
AthleticEvents longjump = new Jumps("Long Jump", "Long Jump Pit");
AthleticEvents hammerThrow = new Throws("Hammer Throw", "Hammer");

hundredMSprint.eventDescription();
halfMarathon.eventDescription();
longjump.eventDescription();
hammerThrow.eventDescription();
}
}


// cannot be instantiated, it is abstract
abstract class AthleticEvents {
private String eventName;

AthleticEvents(String eventName){
this.eventName = eventName;
}

protected String getEventName() {
return this.eventName;
}

// abstract class, derived classes must implement the method
protected abstract void eventDescription();
}

class Track extends AthleticEvents {
private int distanceInMetres;

Track(String eventName, int distanceInMetres) {
super(eventName);
this.distanceInMetres = distanceInMetres;
}

protected int getDistanceInMetres() {
return this.distanceInMetres;
}

// specific implementation overrides the parent class definition
@Override
public void eventDescription(){
System.out.println("Description in class Track about the track event " + this.distanceInMetres + " metres");
}
}

abstract class Field extends AthleticEvents {

Field(String eventName) {
super(eventName);
}

// specific implementation overrides the parent class definition
@Override
public void eventDescription(){
System.out.println("Description in class Field; conveys generic information about field events");
}
}

class Throws extends Field {
private String equipment;

Throws(String eventName, String equipment) {
super(eventName);
this.equipment = equipment;
}

protected String getEquipment() {
return this.equipment;
}

// specific implementation overrides the parent class definition
@Override
public void eventDescription(){
System.out.println("Description in class Throws about the field event " + this.getEventName() + " uses " + this.equipment);
}
}

class Jumps extends Field {
private String infrastructure;

Jumps(String eventName, String infrastructure) {
super(eventName);
this.infrastructure = infrastructure;
}

protected String getInfrastructure() {
return this.infrastructure;
}

// specific implementation overrides the parent class definition
@Override
public void eventDescription(){
System.out.println("Description in class Jumps about the field event " + this.getEventName() + " performed in the " + this.infrastructure);
}
}

Dynamic dispatch is applied to resolve the method that is to be invoked. It is possible that the derived class may override the functionality defined in the parent class; or will be required to implement the parent class function in case of an abstract class or an interface

Method declaration binding happens during compile time, and this is known as early binding; while method implementation occurs at runtime, which is known as late binding or dynamic method dispatch. It is dynamic in the sense that based on the object type, the appropriate method is bound and invoked. Thus, a single function can take many forms

Output; each derived class overrides the method eventDescription()

Since method declaration binding happens at compile time, only methods known to the reference object can be invoked. Consider the following where a reference of the Class AthleticEvents invokes getEquipment() on an Object of Class Throws

public class Main {
public static void main(String args[]){
AthleticEvents hammerThrow = new Throws("Hammer Throw", "Hammer");

// Get the event description
hammerThrow.eventDescription();

// Additionally invoke the getEquipment method
System.out.println(hammerThrow.getEquipment());
}
}

On compiling, the result is —

This is because the reference type of Class AthleticEvents has no method named getEquipment(), and at compile time, it is an unknown symbol.

It can be guaranteed that the method eventDescription() will have an implementation in the derived classes since it is abstract in nature.

The most specific implementation of the method will be bound and invoked. In the following code snippet, the functions eventDescription() is commented in Classes Throws and Jumps, as

package Events; 

public class Main {
public static void main(String args[]){
AthleticEvents longjump = new Jumps("Long Jump", "Long Jump Pit");
AthleticEvents hammerThrow = new Throws("Hammer Throw", "Hammer");

longjump.eventDescription();
hammerThrow.eventDescription();
}
}

// cannot be instantiated, it is abstract
abstract class AthleticEvents {
private String eventName;

AthleticEvents(String eventName){
this.eventName = eventName;
}

protected String getEventName() {
return this.eventName;
}

// has a generic implementation
protected abstract void eventDescription();
}

abstract class Field extends AthleticEvents {

Field(String eventName) {
super(eventName);
}

// specific implementation overrides the parent class definition
@Override
public void eventDescription(){
System.out.println("Description in class Field; conveys generic information about field events");
}
}

class Throws extends Field {
private String equipment;

Throws(String eventName, String equipment) {
super(eventName);
this.equipment = equipment;
}

protected String getEquipment() {
return this.equipment;
}

// specific implementation commented
// @Override
// public void eventDescription(){
// System.out.println("Description in class Throws about the field event " + this.getEventName() + " uses " + this.equipment);
// }
}

class Jumps extends Field {
private String infrastructure;

Jumps(String eventName, String infrastructure) {
super(eventName);
this.infrastructure = infrastructure;
}

protected String getInfrastructure() {
return this.infrastructure;
}

// specific implementation commented
// @Override
// public void eventDescription(){
// System.out.println("Description in class Jumps about the field event " + this.getEventName() + " performed in the " + this.infrastructure);
// }
}

The execution results in the following —

You might have expected an exception to be thrown, since the Classes Throws and Jumps do not have an implementation of eventDescription(), however, considering the resolution process in dynamic dispatch, the implementation of the function is searched through in the derived class, and all its ancestors.

C++

In C++ dynamic method, dispatch is achieved using the keyword virtual. This indicates that the compiler needs to bind and invoke the method or destructor that is implemented in the derived class. A Virtual Table or V-table is maintained to perform Dynamic binding.

--

--

NamyaLG

Tech-enthusiast | Runner_for_life | #NoHumanIsLimited