📖 Polymorphism in JavaScript
Polymorphism is a fundamental concept in Object-Oriented Programming (OOP) that allows objects to be treated as instances of their parent class, even if they have different implementations of the same method. The term "polymorphism" comes from the Greek words "poly" (many) and "morph" (form), meaning that an object can take on many forms.
Polymorphism is essential for creating flexible and reusable code. It allows developers to write more generic and abstract code, which can work with objects of different types, as long as they share a common interface.
Benefits of Polymorphism
- Code Reusability
- Polymorphism allows you to reuse code by writing more generic functions that work with multiple types of objects, as long as they share the same interface.
- Flexibility and Extensibility
- Polymorphism makes it easier to extend your code by adding new subclasses that implement existing methods. This approach allows you to add new functionality without modifying existing code.
Method Overriding (Runtime Polymorphism)
In JavaScript, polymorphism is primarily achieved through method overriding. Method overriding allows a subclass to provide a specific implementation of a method that is already defined in its superclass. This is a form of runtime polymorphism because the method that gets executed is determined at runtime, depending on the type of the object.
class Animal {
sound() {
return 'Some generic sound';
}
}
class Dog extends Animal {
sound() {
return 'Bark';
}
}
const myDog = new Dog();
console.log(myDog.sound()); // Outputs: Bark
In this example, the Dog
class overrides the sound
method inherited from the Animal
class. When myDog.sound()
is called, the Dog
class's implementation of sound
is executed, demonstrating polymorphism in action.
Polymorphism in Action
Polymorphism allows different subclasses to provide different implementations of the same method, which can be used in a unified way. This means that you can write code that interacts with a general interface, and the specific implementation details are handled by the subclasses.
class Animal {
sound() {
return 'Some generic sound';
}
}
class Dog extends Animal {
sound() {
return 'Bark';
}
}
class Cat extends Animal {
sound() {
return 'Meow';
}
}
const animals = [new Dog(), new Cat(), new Animal()];
animals.forEach(animal => console.log(animal.sound()));
// Outputs:
// Bark
// Meow
// Some generic sound
Here, we have an array of Animal
objects, but each object is actually an instance of a different subclass (Dog
, Cat
, or Animal
). When we iterate through the array and call the sound
method on each object, the appropriate method for each subclass is executed. This is polymorphism at work—each object behaves according to its actual class, even though we're treating them all as Animal
objects.
Summary
- Polymorphism
- Polymorphism allows objects of different types to be treated as instances of a common parent class, enabling flexible and reusable code.
- Method Overriding
- Method overriding is a key mechanism for achieving polymorphism in JavaScript, allowing subclasses to provide specific implementations of methods defined in their parent classes.
- Benefits
- Polymorphism enhances code reusability and flexibility, making it easier to extend and maintain your code.
Putting It Into Action
Polymorphism in a Payment System
To see polymorphism in action, let's implement a payment processing system where different payment methods (e.g., CreditCard, PayPal) implement the same interface (processPayment
) but behave differently.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Payment System</title>
</head>
<body>
<h1>Payment Processing System</h1>
<div id="output"></div>
<script>
class Payment {
processPayment(amount) {
throw new Error('Method not implemented');
}
outputToDOM(message) {
const outputDiv = document.getElementById('output');
const p = document.createElement('p');
p.textContent = message;
outputDiv.appendChild(p);
}
}
class CreditCardPayment extends Payment {
processPayment(amount) {
const message = `Processing credit card payment of $${amount}`;
console.log(`Processing credit card payment of $${amount}`);
this.outputToDOM(message);
}
}
class PayPalPayment extends Payment {
processPayment(amount) {
const message = `Processing PayPal payment of $${amount}`;
console.log(`Processing PayPal payment of $${amount}`);
this.outputToDOM(message);
}
}
const payments = [new CreditCardPayment(), new PayPalPayment()];
payments.forEach(payment => payment.processPayment(100));
// Outputs:
// Processing credit card payment of $100
// Processing PayPal payment of $100
</script>
</body>
</html>
In this example, the processPayment
method is implemented differently by each payment method, but they can be used interchangeably thanks to polymorphism.
Challenge
Now that you’ve seen how polymorphism works in a payment system, here’s a challenge:
- Add another payment method (e.g., BitcoinPayment) to the payment system and implement the
processPayment
method for it. - Think of other scenarios where polymorphism could simplify your code and apply it to a small project.
In order to check your learning, you should attempt to create a solution before revealing the provided solution below.
// JavaScript Code for the Challenge Solution
class BitcoinPayment extends Payment {
processPayment(amount) {
console.log(`Processing Bitcoin payment of $${amount}`);
}
}
const bitcoinPayment = new BitcoinPayment();
bitcoinPayment.processPayment(100); // Outputs: Processing Bitcoin payment of $100