In real life, a woman can have different roles. She can be a mother, an employee, and a wife at the same time. The same woman will perform different functions depending on her particular role at the moment. This is the concept of polymorphism, being able to take on different forms.
Let’s imagine that we’ll need to write a program that’ll calculate the area and perimeter of shapes. To do this, we’ll need to define methods to calculate the area and perimeter of our shapes;
perimeter(). However, basic knowledge of geometry tells us that we can’t solve for the area and perimeter of different shapes in the same way. For instance, the perimeter of a circle is
2 x Pi x radius and the perimeter of a square is
4 x Length.
So, we’ll need to define the different shapes as sub-classes or derived classes of the base class shapes. Therefore, we’ll have a subclass circle, square, trapezium, polygon which will all have their methods and different parameters. What we have done above is polymorphism. Our class shapes now has different forms and characteristics like circle, square, trapezium, and polygon as sub-classes.
Polymorphism and Object-Oriented Programming
To understand how polymorphism works in object-oriented programming, it’ll be best if we understand the concept of the object-oriented model. The object-oriented model relies on the concept of objects and classes. This type of model can mutate its data fields with the notion of
In object-oriented programming, we can create a class that calculates the area and perimeter of different shapes with the block of code below:
We can recreate the program from our code above with ECS in pseudocode denoting a data-oriented language.
Polymorphism and Inheritance
Inheritance is an important feature in polymorphism in object-oriented programming. Let’s look at an example of a
car object will have different sub-classes for different car makes like BMW, Toyota, Bentley, etc., and different properties like color and speed:
Next, we can input the color, speed, and make for our different cars.
From our example, a child class can take up a property from the superclass, to define it. Inheritance can grow from present family to grandparents or even great-grandparents.
1. Prototypal Inheritance
Prototypal inheritance is a type of inheritance that adds new properties and methods to an existing object. This inheritance makes use of prototype objects i.e.
2. Pseudoclassical Inheritance
Psuedoclassical inheritance is similar to prototypal inheritance. This type of inheritance emulates classical inheritance using prototypal inheritance. However, if you’re programming with ES6, the use of psuedoclassical inheritance isn’t encouraged since you can use conventional classes (the class keyword). In psuedoclassical inheritance, we try to create a class with a function that is intended to be called with the new keyword. To understand this better, we’ll be using our car example. Let’s imagine we have a car object as shown in the code below:
We can create sub-types of different
car makes objects with prototype using the
With prototype, we have created new objects with the different
car makes. Next, we’ll understand how to pass the prototype as inheritance and how it affects polymorphism.
First, we’ll create a function called
dialogue that we’ll want our cars to inherit:
After that, we’ll allow our cars to inherit the
dialogue function with prototype:
The program above should output “I am a red Toyota with 100mph speed”, “I am a green BMW with 90mph speed”, and “I am a white Bentley with 120mph speed” accordingly in your console.
3. Functional Inheritance
Functional inheritance involves inheriting features with the use of an augmenting function to an object instance:
Polymorphism and Encapsulation
Since we have gotten a better understanding of how inheritance in polymorphism work, it’ll be easier to understand how encapsulation works in polymorphism. While programming, there’ll be a need to bundle or put data together in such a way that users can’t access state values of the variables of a particular object from outside.
From the example below, we’re validating the marks of a student by bundling their data together and inheriting them using prototype-based polymorphism.
A lot of people don’t understand that there’s a difference between abstraction and encapsulation. In abstraction, only certain information is shown while the rest is hidden. Whereas in encapsulation, data is bundled into a single entity and hidden from outside reach. The main reason for encapsulation is to control and validate data. Just as in our example above, validation of student score is done in such a way that the student/public can’t interfere.
Ad-hoc polymorphism is a type of polymorphism that allows a value to exhibit different behaviors when “viewed” at different types. Ad-hoc polymorphism can involve a function taking on different forms but bearing the same name.
This type of polymorphism is oftentimes called overloading. Let’s look at a type of ad-hoc polymorphism called operator overloading.
From the example above, the operator
+ is taking up different forms of adding numbers and concatenating Strings.
Parametric polymorphism is a type of polymorphism that deals with generic functions and generic data types while still maintaining static type safety. Generic functions and data types are functions or data types that can be written in a way so that they can handle values similarly without classifying them based on their type.
For example, objects can store different data types. It doesn’t distinguish its values based on their types:
From the code above, we have an object named after a person Ann. The object contains Ann’s first and last name which are Strings, Ann’s age which is a number, and a Boolean which states that Ann isn’t an adult. Although there are different data types in our object Ann, the object handles them similarly.
const Ann = [‘Ann’, ‘Kim’, 4, false];
Just as in our object example, our array example consists of various data types all treated similarly. If you run
console.log(Ann) for our object or array, you should get a list of the elements.
Let’s look at another example below,
In the example above,
id doesn’t distinguish the values
foo according to their types. So,
id can be in different data types; strings or numbers etc.
For example, if a relative unfortunately died and left you his bookstore. You can read all the books there, sell off the books if you like, you can look at the deceased accounts, the deceased customer list, etc. This is inheritance; you have everything the relative had. Inheritance is a form of code reuse.
If your relative doesn’t leave the bookstore for you in his will, you can reopen the bookstore yourself, taking on all of the relative’s roles and responsibilities, add even some changes of your own — this is subtyping; you are now a bookstore owner, just like your relative used to be.
Let’s look at the example below;
Cat, dog and goat are subtypes of animals. An animal can either be a cat, dog or goat etc. You can substitute different implementations for our different animals.
1. Polymorphic functions affect the performance of your code, i.e. how fast your program runs. For instance, a monomorphic function will run faster compared to a polymorphic function. In some cases, this difference in performance may be insignificant if they’re frequently run.
2. Sometimes, polymorphism reduces the readability of a program. To solve this problem, it is important to comment on your code so that people can identify the runtime behavior of the program.
person can be inherited by a subclass
const Ann = [‘Ann’, ‘Kim’, 4, false];
Also, with polymorphism comes the ability to compose powerful abstractions from simpler ones.
In projects where high performance is not crucial, the design decision whether to use polymorphism or not depends mainly on the approach which will make the code more extensible and maintainable.
In the cases where performance is top priority, it becomes the main driving force to design and architectural decisions.
Since the library is integrated into other products, it needs to be highly optimized not to cause any negative performance impact during runtime. This means that polymorphism and high levels of abstractions are avoided while efficiency and performance are a top priority.
The platform, on the other hand, can utilize abstractions and other methodologies to make the code easily scalable.
There is a free trial if you’d like to give SessionStack a try.
If you missed the previous chapters of the series, you can find them here: