"In object-oriented programming, polymorphism is the provision of a single interface to entities of different types" – Wikipedia

In my experience, polymorphism is the provision of a single definition to engineers who have no idea what that definition means. And I'm not saying that learning a term is a bad habit, but rather that the meaning behind it is more important than the definition itself.

Continuing this debate, when asked to provide examples of polymorphism, the answers often boil down to "Method Overriding" and "Method Overloading". But we're still not addressing what polymorphism actually is. I could use method overriding my entire life without realizing whether it's inheritance, polymorphism, both, or neither.


Method Overloading

Method overloading in Apex allows the creation of multiple methods with the same name but different parameters. This is particularly useful when you have similar logic that applies to different parameter types. Instead of creating separate methods with distinct names, you can combine them under a single method name.

For example, consider the Apex String class, which includes an overloading of the valueOf() method. Depending on the type of the variable passed, different operations are performed, all while using the same method name.

In this case, the overloaded methods are defined at compile time. The methods are explicitly declared, allowing you to easily predict the outcome during code execution. This form of polymorphism is known as "Static Polymorphism". While it’s true that here polymorphism refers to these "different forms", the true beauty of polymorphism reveals when the behavior of the code becomes less predictable.

Method Overriding

Method Overriding represents a more interesting structure, but we often demonstrate it incorrectly, why so? This example involves inheritance, another fundamental concept of Object-Oriented Programming (OOP) and that's why method overriding is often mistakenly presented as an example of inheritance rather than a true example of "Dynamic Polymorphism".

To begin, let's create a class called Parent. It will include a constructor to specify the age and a method that we will attempt to override in the child class.

As you may have noticed, we used the virtual keyword for both the class and the method. Why is this necessary? In Apex (unlike Java), all classes and methods are final by default. Therefore, if you want to extend a class, such as inherit from it or override a method — you must use the virtual keyword to explicitly indicate that the class or method is intended for extension.

In the next step, we will create a Child class that extends the Parent class and overrides the giveAdvice() method that you saw earlier in the parent class.

Finally, we come to the point of method overriding, where we have two classes, each with the same method but different logic inside. Sounds like polymorphism, correct? Well, not really.

Do you remember I mentioned earlier that this example is often presented incorrectly? This is that moment. After implementing the second class and overriding the method, many show the code and say, "Look, this what Dynamic Polymorphism is'.

No, it’s not. You’ve explicitly defined two classes and their logic and how the hell is it supposed to be something not very predictable? True dynamic polymorphism is only demonstrated when you show how it’s used in practice. Specifically:

In the first method, we introduce exactly this "Dynamic Polymorphism". This is because, even though we specify an instance of the "Parent" class as a parameter, Apex allows us to pass any extended class as well, like this:

In the first version of the method, we introduce this "Dynamic Polymorphism". Even though the extractAdvice() method accepts an instance of the Parent class, Apex also allows us to pass an instance of any class that extends Parent, like this:

Parent newChild = new Child(5);
extractAdvice(newChild);

We create a Child instance using new Child(5), but store it in a variable declared as Parent. This is possible because every Child is also a Parent.

When extractAdvice() calls giveAdvice(), Apex determines the actual type of the object at runtime. Since the object is a Child, Apex executes the overridden Child.giveAdvice() method, which returns "Have Fun". This behavior is called "Dynamic Polymorphism" (or runtime polymorphism).

In the second version of extractAdvice(), we use if-else conditions to determine which code should be executed. This is not polymorphism. You can create the correct class hierarchy but still use it incorrectly by manually checking object types instead of allowing Apex to select the appropriate implementation at runtime.

Dynamic Instantiation

While discussing the topic of Dynamic Polymorphism, I would also like to touch on instantiating a class at runtime. Some argue that this is not strictly part of polymorphism, though I disagree.

Consider the case where we have a class name represented as a string, like this:

String className = 'MyClass';

How would you create an instance of this class in this scenario? Salesforce provides us with a great class called Type that allows us to do this easily and seamlessly. Let's put it into practice:

In this example, we pass our class name to the method as a string, and using the Type class, we can instantiate it with a combination of the getName() and newInstance() methods.

But wait... If we instantiate this class dynamically, how did I manage to specify the name GoogleAuthorizer statically? How did I know?

It’s all thanks to the fact that GoogleAuthorizer is not a class, but an interface.

This means that when using this method, we must ensure that any class passed as a string strictly adheres to this particular interface. This allows us to predict exactly what methods it contains.

In this case, I am confident that the passed class will include the method retrieveAccessToken() because it is part of the interface, and as we know, everything defined in an interface must be implemented by the class that implements it.

What Exactly is Polymorphism?

As you may have noticed, it comes in two types: Static Polymorphism and Dynamic Polymorphism. Furthermore, the term "Polymorphism" is not limited to "different forms"; it represents a structure in which our code becomes less predictable and more generalized, making it easier to reuse and more flexible to extend.