Inheritance in TypeScript

Inheritance is a key aspect of object-oriented programming that allows a class to inherit properties and methods from another class, promoting code reuse and hierarchy. Through the use of the extends keyword, a derived class can inherit the members of a base class and can further extend or override them. This mechanism supports the creation of a hierarchical structure where a subclass inherits the behavior of its superclass, allowing for the development of more specialized classes while maintaining a shared set of characteristics.

TypeScript enforces type safety in inheritance, providing a statically typed way to express relationships between classes and ensuring that derived classes conform to the interface defined by their base classes. The combination of inheritance with other OOP features such as encapsulation and polymorphism contributes to the creation of scalable and maintainable software systems.

Basic Inheritance

class Person { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } greet(): string { return `Hello, my name is ${this.name}!`; } } class Employee extends Person { // Inherits all properties and methods from Person salary: number; constructor(name: string, age: number, salary: number) { super(name, age); // Calls the parent constructor this.salary = salary; } introduce(): string { return `${this.greet()} I'm an employee earning ${this.salary}.`; } } const john = new Employee("John Doe", 30, 50000); console.log(john.greet()); // Outputs "Hello, my name is John Doe!"

This example shows how Employee inherits all properties and methods from Person using the extends keyword. It adds its own salary property and introduce method, building upon the functionality of the parent class.

Constructor Chaining

class Manager extends Employee { department: string; constructor(name: string, age: number, salary: number, department: string) { super(name, age, salary); // Calls parent constructor with necessary arguments this.department = department; } leadMeeting(): string { return `Let's get to work, team! ${this.introduce()}`; } } const jane = new Manager("Jane Smith", 35, 60000, "Marketing"); console.log(jane.leadMeeting()); // Outputs "Let's get to work, team! Hello, my name is Jane Smith! I'm an employee earning 60000."

Here, Manager extends Employee and chains the parent constructor call to initialize inherited properties. It adds a department property and a leadMeeting method showcasing further specialization.

Super Keyword

class Doctor extends Person { specialty: string; constructor(name: string, age: number, specialty: string) { super(name, age); // Calls parent constructor this.specialty = specialty; // Add this line to initialize the specialty property } diagnose(): string { return `Based on my experience as a ${this.specialty}, I recommend...`; } // Accessing parent method with super greet(): string { return `As your doctor, ${super.greet()}! I specialize in ${this.specialty}.`; } } const drMark = new Doctor("Mark Johnson", 40, "Cardiology"); console.log(drMark.greet()); // Outputs "As your doctor, Hello, my name is Mark Johnson! I specialize in Cardiology." console.log(drMark.diagnose()); // Outputs "Based on my experience as a Cardiologist, I recommend..."

This example demonstrates the super keyword. Doctor uses it to access the parent's greet method within its own method, extending its functionality with additional information.

Access Modifiers and Inheritance

class SecretAgent extends Person { private mission: string; // Accessible only within this class constructor(name: string, age: number, mission: string) { super(name, age); this.mission = mission; } public revealCodename(): string { // Accessing private property within the class return `My codename is Agent ${this.name.charAt(0)}${this.mission.length}.`; } } const agentX = new SecretAgent("Alexia", 32, "Top Secret"); console.log(agentX.revealCodename()); // Outputs "My codename is Agent A4." // Cannot access directly: console.log(agentX.mission); // Error

This example showcases access modifiers. While mission is private in SecretAgent, a public method can access it to generate a codename, demonstrating controlled inheritance without revealing sensitive information.

Abstract Classes

TypeScript supports abstract classes, which are classes that cannot be instantiated directly and are meant to serve as base classes for other classes.

abstract class Shape { abstract calculateArea(): number; } class Circle extends Shape { radius: number; constructor(radius: number) { super(); this.radius = radius; } calculateArea(): number { return Math.PI * this.radius ** 2; } } const circle = new Circle(5); console.log(circle.calculateArea()); // Output: 78.54

The Shape class is abstract and enforces that any class extending it must provide its own implementation of the abstract method calculateArea.

Conclusion

TypeScript inheritance allows classes to inherit properties and methods from a base class, facilitating code reuse and creating class hierarchies. It supports method overriding, access modifiers, and abstract classes to enhance code organization and structure.