Polymorphism With TypeScript (OOP)

Polymorphism is a core concept of object-oriented programming that allows objects of different types to be treated as instances of a common base type, enabling flexibility and extensibility in code. TypeScript achieves polymorphism through features like method overriding and interface implementation. Method overriding allows a subclass to provide a specific implementation for a method that is already defined in its superclass, allowing instances of the subclass to be used interchangeably with instances of the superclass.

Interfaces define a contract specifying a set of methods that a class must implement, enabling polymorphic behavior across classes that adhere to the same interface. Polymorphism promotes code reuse, enhances maintainability, and supports the creation of more adaptable and scalable software systems by allowing different objects to be treated uniformly based on their common characteristics.

Method Overriding

Polymorphism is often demonstrated through method overriding, where a derived class provides its own implementation of a method that is already defined in its base class.

class Animal { makeSound(): void { console.log("Some generic sound"); } } class Dog extends Animal { makeSound(): void { console.log("Bark! Bark!"); } } class Cat extends Animal { makeSound(): void { console.log("Meow!"); } } // Polymorphic behavior const animals: Animal[] = [new Dog(), new Cat()]; animals.forEach(animal => animal.makeSound()); // Output: // Bark! Bark! // Meow!

Here, both Dog and Cat extend the Animal class, providing their own implementation of the makeSound method. An array of Animal instances demonstrates polymorphism, as each object behaves differently based on its actual type.

Inheritance and Method Overriding

Subclasses inherit methods from their parent class but can override them with their own implementation.

This allows different objects to respond to the same method call in different ways based on their type.

class Animal { speak(): string { return "This is the sound of an animal."; } } class Dog extends Animal { override speak(): string { return "Woof!"; } } class Cat extends Animal { override speak(): string { return "Meow!"; } } const dog = new Dog(); const cat = new Cat(); console.log(dog.speak()); // Outputs "Woof!" console.log(cat.speak()); // Outputs "Meow!" // Both objects respond to "speak" but with their specific behavior.

In this example, Dog and Cat inherit the speak method from Animal but override it with their own sounds, demonstrating polymorphic behavior through inheritance.

Polymorphism with Interfaces

Interfaces define a contract that classes can implement, allowing objects of different classes to be treated uniformly if they adhere to the same interface.

interface Shape { calculateArea(): number; } class Circle implements Shape { radius: number; constructor(radius: number) { this.radius = radius; } calculateArea(): number { return Math.PI * this.radius ** 2; } } class Square implements Shape { sideLength: number; constructor(sideLength: number) { this.sideLength = sideLength; } calculateArea(): number { return this.sideLength ** 2; } } // Polymorphic behavior const shapes: Shape[] = [new Circle(5), new Square(4)]; shapes.forEach(shape => console.log(shape.calculateArea())); // Output: // 78.54 // 16

In this example, both Circle and Square implement the Shape interface, allowing instances of both classes to be treated uniformly within the shapes array.

Polymorphism with Abstract Classes

Abstract classes can also contribute to polymorphism by providing a common base for derived classes to share common behavior while allowing them to implement specific details.

abstract class Vehicle { abstract start(): void; } class Car extends Vehicle { start(): void { console.log("Car started"); } } class Motorcycle extends Vehicle { start(): void { console.log("Motorcycle started"); } } // Polymorphic behavior const vehicles: Vehicle[] = [new Car(), new Motorcycle()]; vehicles.forEach(vehicle => vehicle.start()); // Output: // Car started // Motorcycle started

The Vehicle abstract class declares an abstract method start, and both Car and Motorcycle provide their own implementations. Instances of these classes can be used polymorphically within the vehicles array.

Advanced Polymorphism

  1. Generics can further enhance polymorphism by defining functions that work with different types at runtime.
  2. Decorators can add functionalities on-demand based on object types, offering flexible behavior modifications.

Conclusion

Polymorphism in TypeScript allows for code that is more flexible and extensible. Objects can be treated uniformly based on shared behavior, enabling the creation of code that adapts to changes and additions in the class hierarchy.