Hello again! In this part of our journey into Object-Oriented Programming with TypeScript, we'll explore inheritance. Inheritance allows us to share code across classes, thus improving readability and efficiency.
TypeScript enhances this process with strong typing, which ensures reliability and consistent behavior across the application. By leveraging TypeScript's type system, we can reinforce the integrity of our inherited attributes and methods, providing a more robust programming experience.
In this lesson, we'll explore attribute and method inheritance in TypeScript using practical examples. Our lesson plan includes defining inheritance, examining attribute inheritance, exploring method inheritance, and understanding the super()
function. Ready? Let's get started!
Inheritance involves creating a child class that inherits properties and methods from a parent class. In TypeScript, we encounter scenarios where classes share common attributes or methods, making inheritance highly useful.
The extends
keyword is used to set up inheritance, allowing one class to inherit properties and methods from another class. Here's an example featuring a parent class named Vehicle
and a child class named Car
, both using TypeScript syntax:
TypeScript1// Define the parent class 'Vehicle' 2class Vehicle { 3 // Initialize the Vehicle with color and brand attributes 4 constructor(public color: string, public brand: string) {} 5} 6 7// Define the child class 'Car', inheriting from 'Vehicle' 8class Car extends Vehicle { 9 constructor(color: string, brand: string, public doors: number) { 10 // Call the parent class's constructor method to set color and brand 11 super(color, brand); 12 } 13}
In the Car
class, the super()
function inside its constructor calls the Vehicle
class's constructor, enabling the inherited properties to be correctly initialized. The extends
keyword signifies that Car
is a subclass of Vehicle
.
In this lesson, our focus will primarily be on single inheritance, where one parent class gives attributes and methods to one child class, highlighting the strengths of type-safe inheritance in TypeScript.
Attribute inheritance allows a child class to inherit the attributes of a parent class, with private fields being protected by TypeScript's access modifiers.
Consider this example featuring a parent class named Artist
, and a child class named Musician
:
TypeScript1class Artist { 2 constructor(public name: string) {} // Parent's attribute 3} 4 5class Musician extends Artist { 6 constructor(name: string, public instrument: string) { 7 super(name); // Inheriting parent's attribute 8 } 9} 10 11const john = new Musician('John Lennon', 'Guitar'); // Creating a Musician instance 12console.log(john.name); // Output: John Lennon 13console.log(john.instrument); // Output: Guitar
However, if the name
attribute in the Artist
class were private, it wouldn't be directly accessible in the Musician
class. Instead, it would be accessed via a method:
TypeScript1class Artist { 2 private name: string; // Private attribute 3 4 constructor(name: string) { 5 this.name = name; // Initialize private attribute 6 } 7 8 getName(): string { // Getter method for the private attribute 9 return this.name; 10 } 11} 12 13class Musician extends Artist { 14 public instrument: string; 15 16 constructor(name: string, instrument: string) { 17 super(name); 18 this.instrument = instrument; // Initialize instrument attribute 19 } 20} 21 22const john = new Musician('John Lennon', 'Guitar'); 23console.log(john.getName()); // Output: John Lennon 24console.log(john.instrument); // Output: Guitar
The Musician
class inherits the name
attribute from the Artist
class and also has its own unique attribute, instrument
. Since name
is private, it is accessed through the getName
method.
Similar to attributes, method inheritance allows a child class to inherit the methods of a parent class. Type annotations enhance this by ensuring parameter and return types are clear and reliable.
Here's an example where the Car
class can invoke the start
method from the Vehicle
class:
TypeScript1class Vehicle { 2 constructor(public brand: string) {} 3 4 start(): void { 5 console.log(`The ${this.brand} is starting.`); 6 } 7} 8 9class Car extends Vehicle { 10 // No new methods or attributes are added here 11} 12 13const myCar = new Car('BMW'); 14myCar.start(); // Output: The BMW is starting.
In this scenario, TypeScript automatically provides a default constructor if none is defined, which internally calls super()
. This ensures that the parent class's constructor is invoked correctly, facilitating seamless inheritance of the brand
attribute in the Car
class.
The super()
function is integral in inheritance for calling parent class methods from a child class. It is particularly useful in method overriding and initialization. It allows a child class to extend or utilize the functionality of a parent class without directly modifying it.
For instance, when overriding a method to add or alter its behavior, super()
enables calling the original method from the parent class to integrate its functionality with new enhancements:
TypeScript1class Vehicle { 2 start(): string { 3 return "Vehicle is starting..."; 4 } 5} 6 7class Car extends Vehicle { 8 start(): string { 9 return super.start() + " Beep! Beep!"; 10 } 11} 12 13const myCar = new Car(); 14console.log(myCar.start()); // Output: Vehicle is starting... Beep! Beep!
Similarly, during initialization, super()
calls the constructor method of the parent class, ensuring that the child class is initialized correctly, allowing the child class to add its specific attributes seamlessly:
TypeScript1class ParentClass { 2 value: string; 3 4 constructor(value: string) { 5 this.value = value; 6 } 7} 8 9class ChildClass extends ParentClass { 10 additionalValue: string; 11 12 constructor(value: string, additionalValue: string) { 13 super(value); // Invoke parent class's constructor 14 this.additionalValue = additionalValue; 15 } 16} 17 18const childClass = new ChildClass("value", "additional_value"); 19console.log(childClass.value); // Output: value 20console.log(childClass.additionalValue); // Output: additional_value
In these ways, super()
facilitates a coherent and modular approach to inheritance by allowing child classes to build upon or adapt the functionality of their parent classes effectively and cleanly in TypeScript.
We've successfully explored attribute and method inheritance in TypeScript and practiced using several examples. Mastering these concepts can enhance both efficiency and readability. Practice is essential for proficiency, so get ready for some practice exercises to solidify your understanding. Programming is all about experimenting, learning, and problem-solving. Enjoy the journey!