5. Polymorphism
Table of Contents:
Key Concepts
Polymorphism is the OOP concept where different types of objects can be treated the same way because they share the same interface (the same method names) even though each type implements those methods differently.
Classes Review
So far in this Object-Oriented Programming unit we've learned:
How to encapsulate data and methods into objects.
How to use both factory functions and classes to create interfaces that provide well-defined and controlled access points for interacting with internal data.
How to use inheritance to reuse and extend class interfaces.
How to identify where methods are defined in a prototype chain.
We can see all of this in action in this prototype chain: MarcyStudent → Student → Person
class Person {
constructor(first, last, age) {
this.firstName = first;
this.lastName = last;
this.age = age;
}
fullName() {
return `${this.firstName} ${this.lastName}`;
}
introduce() {
return `Hi, I'm ${this.firstName} and I'm ${this.age} years old.`;
}
}
class Student extends Person {
courses = [];
constructor(first, last, age, subject, school) {
super(first, last, age);
this.subject = subject;
this.school = school;
}
introduce() {
return `${super.introduce()}. I am studying ${this.subject} at ${this.school}.`
}
enrollInCourse(courseName) {
this.courses.push(courseName);
}
}
class MarcyStudent extends Student {
static validCourses = ['Leadership & Development', 'Technical Interview Prep', 'Technical Lecture'];
constructor(first, last, age) {
super(first, last, age, "Software Engineering", "Marcy Lab School");
}
enrollInCourse(courseName) {
if (!MarcyStudent.validCourses.includes(courseName)) {
return 'Invalid Course Name';
}
super.enrollInCourse(courseName);
}
}Now, let's introduce the final pillar of object-oriented programming: polymorphism.
Polymorphism
Recall that subclasses are considered instances of their superclasses.
const ada = new MarcyStudent('Ada', 'Lovelace', 24);
console.log(ada instanceof MarcyStudent); // true
console.log(ada instanceof Student); // true
console.log(ada instanceof Person); // truePolymorphism is the OOP concept where different types of objects can be treated the same way because they share the same interface (the same method names) even though each type implements those methods differently.
Polymorphism → poly ("many") + morph ("form") → "having many forms"
For example: A
Personcan take "many forms" — it could be aPerson, aStudent, or aMarcyStudent, but they can all be treated as aPersonbecause they share the same interface.
In this example, ada, alan, and reuben each have the method introduce() despite having different implementations. This means that we can write code like this:
const ada = new MarcyStudent('Ada', 'Lovelace', 24);
const alan = new Student('Alan', 'Turing', 27, 'Mathematics', 'Cambridge University');
const reuben = new Person('Reuben', 'Ogbonna', 36);
const people = [ada, alan, reuben];
// This method works with any type of Person
const printIntroduction = (person) => {
console.log(`Introducing ${person.fullName()}:`);
console.log(person.introduce());
};
people.forEach(printIntroduction);
// Introducing Ada Lovelace:
// Hi, I'm Ada and I'm 24 years old. I am studying Software Engineering at Marcy Lab School.
// Introducing Alan Turing:
// Hi, I'm Alan and I'm 27 years old. I am studying Mathematics at Cambridge University.
// Introducing Reuben Ogbonna:
// Hi, I'm Reuben and I'm 36 years old.Polymorphism works because of the prototype chain - when we call person.fullName() or person.introduce(), JavaScript walks up the prototype chain to find the most specific implementation.
Question: Take a look at the example below. How would you explain how it demonstrates Polymorphism?
class Car {
constructor(make, model) {
this.make = make;
this.model = model;
}
drive() {
console.log("Vrooom");
}
}
class RaceCar extends Car {
drive() {
console.log("Vah... Vah...");
super.drive();
console.log("WHEEEEEEE!!!!");
}
}
const car1 = new Car("Chevy", "Cobalt");
const car2 = new RaceCar("Ferrari", "Portofino");
const car3 = new Car("Tesla", "Model X");
const cars = [car1, car2, car3];
cars.forEach((car) => car.drive());
// since they are all Cars, they all have drive, even if they behave differentlyWhy does this matter?
Without polymorphism, a function like printIntroduction would need to confirm that incoming person objects have the fullName() and introduce() methods. Without this check, an error could be thrown for trying to invoke an undefined method.
// Without polymorphism, you'd need to check if methods exist:
const printIntroduction = (person) => {
if (person.fullName && person.introduce) {
console.log(`Introducing ${person.fullName()}`);
console.log(person.introduce());
} else {
console.log("This object can't introduce itself!");
}
}
// With polymorphism: just trust the interface!
const printIntroduction = (person) => {
console.log(`Introducing ${person.fullName()}`);
console.log(person.introduce());
}When to use polymorphism: Polymorphism shines when you're working with collections of related objects (like arrays of different users, or lists of different vehicles) and you want to treat them uniformly. If you find yourself writing lots of if (type === 'X') or instanceof checks, that's a sign polymorphism could simplify your code.
Challenge
Create two classes, User and Admin.
A User should have the following properties:
usernamea string provided to the constructorisOnlinewith a default valuefalse
A User should have the following methods:
loginsetsisOnlinetotrueand prints<username> has logged in!logoutsetsisOnlinetofalseand prints<username> has logged out!
An Admin should be a subclass of User. It should also have:
A property
isAdminset totrueA method called
doSecretAdminStuffthat just prints a message"Doing secret admin stuff".
Then, create a user instance and an admin instance and demonstrate how to use all of their methods.
Finally, create an array containing both the user and admin, and loop through them calling login() on each. Notice how polymorphism lets you treat them uniformly even though one is a User and one is an Admin."
const users = [user, admin];
users.forEach(u => u.login()); // polymorphism in action!Summary
Polymorphism ("many forms") occurs when multiple types of objects share "signatures" (they have the same property and method names).
The impact of polymorphism is that our program can reliably use different types of objects in the same way if they all descend from the same parent class.
Method Overriding means that method signatures are the same even if their implementations are different
class Car {
constructor(make, model) {
this.make = make;
this.model = model;
}
drive() {
console.log("Vrooom");
}
}
class RaceCar extends Car {
drive() { // Method Override
console.log("Vah... Vah...");
super.drive(); // invoke the parent class method
console.log("WHEEEEEEE!!!!");
}
}
const car1 = new Car("Chevy", "Cobalt");
const car2 = new RaceCar("Ferrari", "Portofino");
const car3 = new Car("Tesla", "Model X");
const cars = [car1, car2, car3];
cars.forEach((car) => car.drive());
// since they are all Cars, they all have drive, even if they behave differentlyLast updated