PolymorphismReading Only
Any "good" software paradigm aims to abstract away complexity and hide details that aren't needed. Polymorphism is another manifestation of that idea and maybe even the most powerful advantage of OOP.
Categorization and Substitution
If you're like me, you categorize things in your life. I'm sure we both group animals into categories like mammals, birds, and fish. And we group vehicles into trucks, motorcycles, buses, and cars. Categorization allows us to simplify our understanding of the world by grouping similar items or concepts, enabling us to treat these categories as a single entity and avoiding the intricate details.
If I told you I have an animal, you know I have a creature who breathes in oxygen and eats. How it breathes in oxygen (fish with gills, land animals with lungs) and what it eats only matters if we start talking about what type of animal I have.
This simplification is possible because of substitution. We can do the same with our code; we can reference an object as its generalizable category, resulting in polymorphism. This allows us to build generic code that can work with different derived classes without knowing the specific implementation. In other words, we can make substitutions in our code at runtime.
Polymorphism in Action
Let's expand on the SubscriptionProduct
and Product
examples from before.
Here's the scenario: You're asked to build a method that can take in a list of products and calculate the total cost of the products.
public Decimal calculateTotalCost(List<Product> products){ .... }
In addition to the SubscriptionProduct
we know about, there are other products like HardwareProduct
and ServiceProduct
. Each have their unique implementations for the total cost method:
public class HardwareProduct extends Product { public HardwareProduct(String name, Decimal price){ super(name, price); } public Decimal calculateTotalPrice(){ return price * .2; // just an arbitrary difference } } public class ServiceProduct extends Product { Integer hours; public ServiceProduct(String name, Decimal price, Integer hours){ super(name, price); this.hours = hours; } public Decimal calculateTotalPrice(){ return price * hours; } }
Now because they all belong to the Product
category, We can reference them like this:
public class ShoppingCartController { public void buildShoppingCart(){ SubscriptionProduct os = new SubscriptionProduct('Operating System', 500.00); ServiceProduct itSupport = new ServiceProduct('IT Support', 150.00, 100); HardwareProduct laptop = new HardwareProduct('Laptop', 899.00); List<Product> products = new List<Product>(); products.add(os); products.add(itSupport); products.add(laptop); Decimal total = calculateTotalCost(products); // other processing ... } public Decimal calculateTotalCost(List<Product> products){ Decimal runningTotal = 0; for(Product p : products){ runningTotal += p.calculateTotalPrice(); } return runningTotal; } }
There are two ways this code is polymorphic.
Product Inheritance:
The code defines multiple classes inherited from a common base class, Product
. Specifically, the classes SubscriptionProduct
, ServiceProduct
, and HardwareProduct
all inherit from Product
. This means we can create objects of these derived classes and treat them as objects of the base class Product
. This is an example of polymorphism because you can use a generic reference to Product
to hold and operate on objects of any of its derived classes, and the appropriate method implementations are invoked based on the actual object type.
For example, when you add instances of SubscriptionProduct
, ServiceProduct
, and HardwareProduct
to the products list, you're taking advantage of polymorphism:
List<Product> products = new List<Product>(); products.add(os); // os is an instance of SubscriptionProduct products.add(itSupport); // itSupport is an instance of ServiceProduct products.add(laptop); // laptop is an instance of HardwareProduct
This allows us to treat all these objects as Product
instances, even though they are derived differently.
Method call:
The calculateTotalPrice
method in the ShoppingCartController
class takes a list of Product
objects and calls the calculateTotalPrice()
method on each of them. Since each derived class (SubscriptionProduct
, ServiceProduct
, and HardwareProduct
) has its own implementation of the calculateTotalPrice()
method, the appropriate method is called based on the actual object type during the iteration.
for(Product p : products){ runningTotal += p.calculateTotalPrice(); }
In this loop, the calculateTotalPrice()
method is polymorphic, and the correct implementation for each product type is called, making it possible to calculate the total cost accurately for products of different types.
Polymorphism is a powerful concept that allows for flexibility and extensibility in object-oriented programming, as it allows you to work with different types of objects through a common interface or base class, and the actual behavior is determined at runtime based on the specific object type. Again, don't worry about the syntax. I just want you to focus on the idea and the problem to be solved here. I'll show you how abstract classes and interfaces work in Apex in detail in the next course.