Language: EN

que-es-y-cuando-usar-polimorfismo-en-programacion

What is and how to use polymorphism

The polymorphism is the last pillar that we need to see of object-oriented programming. Polymorphism is the ability of an object to behave in different ways depending on the context in which it is used.

Which is a very theoretical definition, but not very practical. In other words,

Polymorphism is a very complex way of saying that a mayor is a person

We have to recognize that polymorphism, as a word, sounds very good. It’s one of those words that makes you feel smart just by saying it. It sounds great 🤯. But, in reality, it’s not as difficult as it seems at first.

Basically, polymorphism consists of

An object of a child class of another can occupy a variable of the parent class, but preserving its behavior.

Okay, that still sounds complicated. Let’s see it with a code example. Let’s go for another classic of OOP: fruit examples!

Suppose we have a parent class Fruit, and two derived child classes Apple and Orange

class Fruit
class Apple   extends  Fruit
class Orange  extends  Fruit
/// ... other fruits

What polymorphism says is that an Orange can occupy the place (a variable) of type Fruit. So,

// 👍 normal case, fruit object in fruit variable
Fruit myFruit = new Fruit();

// 🎭 POLYMORPHISM! I can put an orange in a fruit-type variable
Fruit myFruit = new Orange();

// ❌ you can NOT do this, because not all fruits are oranges
Orange myOrange = new Fruit();

Or, going back to the example of the mayor, if you have a bus where Person can get on, Mayors can get on. And students. And plumbers. Because (hippie moment 🦄🌈) they are all people.

curso-poo-autobus

A bus carries people (with feelings)

How polymorphism works

Putting derived objects in variables of one of their parent classes is only half the “fun”. To finish seeing polymorphism, we need to talk about how an object behaves when it occupies a variable of another type that is not its own.

For this, we have to remember that child classes, in addition to inheriting properties and methods from their parents, can override some or all of them.

For example, let’s suppose that Fruit and Apple have a very simple method, which writes their name on the console. “Fruit” and “Apple” respectively.

class Fruit 
{
    TellYourName() { console.write("Fruit") }
}

class Apple extends Fruit
{
	// override the method
    TellYourName() { console.write("Apple") }
}

What we want to know is what happens when we play to put objects of one type into another type. We have the three valid cases.

// "normal" fruit
Fruit myFruit = new Fruit();
myFruit.TellYourName();                   // 👍 Easy case, prints "Fruit"

// "orange" orange
Orange myOrange = new Orange();     
myOrange.TellYourName();                 // 👍 Easy case, prints "Orange"

// here's the trouble, orange as fruit
Fruit myOrangeFruit = new Orange();
myOrangeFruit.TellYourName();            // 🎭 POLYMORPHISM, prints "Orange"

Let’s see what happened:

  • 👍Fruit as fruit, and orange as orange, have no more mystery. Each one calls its corresponding method, and that’s it.
  • 🎭The “complicated” case is an Orange stored in Fruit. Here comes the polymorphism, and the result is that it prints “orange”.

Which is not really that complicated. When calling a method, it calls the method of the object you have. The variable type doesn’t matter, what matters is what you really have stored in that variable.

Or, in other words, if you ask a mayor, a teacher, or a plumber what their profession is, they will tell you their profession. It doesn’t matter if they are sitting in their office chair, in the bus seat, or on a park bench.

Do you understand? Follow me for more advice from people sitting in chairs 🪑.

When to use polymorphism

There are different situations where you can use polymorphism. Covering all cases would be impossible. But I’m going to tell you about two important ones, which you will use frequently. They are also good examples of polymorphism usage.

For the example, imagine that you have a geometric shape Figure, specialized in three child classes Rectangle, Triangle, and Circle.

class Figure { 	
	Draw() { /* ... */ }
	
	/* more things here... */
}

class Rectangle extends Figure { 	
	Draw() { /* ... */ }
}

class Triangle extends Figure { 	
	Draw() { /* .. */ }
}

class Circle extends Figure { 	
	Draw() { /*... */ }
}

They all have their Draw() method overridden to draw correctly on the screen, each with its own peculiarities.

Functions with parent class parameter

The first example is to make a function that receives a Figure, and therefore can receive Rectangle, Triangle, and Circle.

function ProcessFigure(Figure figure)
{
	figure.Draw();
	
	// do other things with figure
}

By having the method overridden, the function simply calls Draw(), and each object will be drawn correctly.

This way the ProcessFigure function doesn’t have to know the details of how to draw the objects, each object knows that. It only knows that it wants to draw objects, not how to do it.

It also allows us to reuse the code.

Collections of parent classes

Another very common example is to have a collection of elements where we want to store several more specific types. For example, you have a collection of Figure items.

Figures[] FigureList;

Thanks to polymorphism, inside we can store objects of type Rectangle, Triangle, Circle.

Finally, when we want to draw them, we go through the entire collection and invoke the Draw() method.

FigureList.foreach(Draw);

Each figure will be drawn correctly, by calling the appropriate method.

Example of polymorphism in different languages

Let’s see how the syntax would be to do polymorphism between classes in different programming languages.

In C++, we define the base class Fruit with a virtual method TellYourName() that returns “I am a fruit”. Then we create a subclass called Orange that inherits from Fruit and overrides the TellYourName() method returning “I am an orange”

// Definition of the base class Fruit
class Fruit {
public:
    // Virtual method that shows generic information about the fruit
    virtual std::string TellYourName() {
        return "I am a fruit";
    }
};

// Definition of the subclass Orange that inherits from Fruit
class Orange : public Fruit {
public:
    // Overridden method that shows specific information about the orange
    std::string TellYourName() override {
        return "I am an orange";
    }
};

int main() {
	// Create instances of Fruit and Orange
    Fruit* fruit = new Fruit();
    Fruit* orange = new Orange();

    std::cout << fruit->TellYourName() << std::endl; // Output: I am a fruit
    std::cout << orange->TellYourName() << std::endl; // Output: I am an orange
}

The case of C# is very similar to the previous one. The base class Fruit has a virtual method TellYourName(). The Orange class inherits from Fruit and overrides the method with its own TellYourName().

// Definition of the base class Fruit
public class Fruit
{
    public virtual string TellYourName()
    {
        return $"I am a fruit";
    }
}

// Definition of the Orange subclass that inherits from Fruit
public class Orange : Fruit
{
    // Overridden method that shows specific information about the orange
    public override string TellYourName()
    {
        return $"I am an orange";
    }
}

// Create instances of Fruit and Orange
Fruit fruit = new Fruit();
Fruit orange = new Orange();

Console.WriteLine(fruit.TellYourName()); // Output: This is a fruit called Apple.
Console.WriteLine(orange.TellYourName()); // Output: This is a fruit called Orange. It is orange.

Now let’s see polymorphism in JavaScript. Again, we create the base class Fruit with its TellYourName() method. The Orange class extends the Fruit class and overrides the method.

// Definition of the base class Fruit
class Fruit {
    // Virtual method that shows generic information about the fruit
    TellYourName() {
        return "I am a fruit";
    }
}

// Definition of the Orange subclass that inherits from Fruit
class Orange extends Fruit {
    // Overridden method that shows specific information about the orange
    TellYourName() {
        return "I am an orange";
    }
}

// Create instances of Fruit and Orange
const fruit = new Fruit();
const orange = new Orange();

// Call the TellYourName() method on both instances
console.log(fruit.TellYourName()); // Output: I am a fruit
console.log(orange.TellYourName()); // Output: I am an orange

Finally in Python we can also define our Fruit class with a tell_your_name() method. Then we create a Orange subclass that inherits from Fruit and overrides the tell_your_name() method.

# Definition of the base class Fruit
class Fruit:
    # Virtual method that shows generic information about the fruit
    def tell_your_name(self):
        return "I am a fruit"

# Definition of the Orange subclass that inherits from Fruit
class Orange(Fruit):
    # Overridden method that shows specific information about the orange
    def tell_your_name(self):
        return "I am an orange"

# Create instances of Fruit and Orange
fruit = Fruit()
orange = Orange()

# Call the tell_your_name() method on both instances
print(fruit.tell_your_name())  # Output: I am a fruit
print(orange.tell_your_name())  # Output: I am an orange

As we can see, the concepts of polymorphism are more or less similar in all languages that include object-oriented programming, beyond the differences in syntax.

Where does polymorphism come from?

Just as we have done with the other pillars, let’s see the reasons that led to the invention of POLYMORPHISM. Remember that people used to group data, but if anyone could modify it, very interesting and unmanageable messes were created.

pilares-oop

The four pillars of OOP

So they invented ENCAPSULATION. Being encapsulated, each class had to be independent, which meant having a lot of duplicated code. To allow code reuse, INHERITANCE was introduced.

But inheritance alone does not prevent us from repeating code. For example, in the case of Rectangle, Triangle, Circle, we would have to do all our functions repeated for each type. To avoid this, POLYMORPHISM is introduced.

In summary:

  • Encapsulation prevents objects from being touched in an uncontrolled manner
  • Inheritance allows us not to repeat the code of the classes
  • Polymorphism completes inheritance, and allows me not to have to repeat the code of what uses my classes (collections, functions)

Finally, from the point of view of abstraction and modeling, POLYMORPHISM makes sense. Thanks to INHERITANCE: an Orange has the same properties as Fruit, because it is a specialization of Fruit (inheritance).

In addition, POLYMORPHISM adds an action like “eat fruit”, also functions with Apples or Oranges. Equally a “bag to store fruit”, can have Apples or Oranges inside.