Typescript Inheritance And Abstract Classes Complete Guide
Understanding the Core Concepts of TypeScript Inheritance and Abstract Classes
TypeScript Inheritance and Abstract Classes: A Detailed Guide
Understanding Inheritance
Inheritance is a fundamental concept in object-oriented programming that allows objects or classes to inherit properties and methods from other objects or classes. This mechanism promotes code reusability and establishes a hierarchical relationship between classes.
- Extending Classes: When a class inherits from another class, it is said to extend or subclass that class. The subclass includes all the properties and methods of the superclass (also known as the base class), unless those properties and methods are overridden or hidden.
Example:
class Animal {
type: string;
constructor(type: string) {
this.type = type;
}
makeSound(): void {
console.log("Some generic sound");
}
}
class Dog extends Animal {
breed: string;
constructor(type: string, breed: string) {
super(type); // Calls the parent class' constructor
this.breed = breed;
}
makeSound(): void {
console.log("Woof woof");
}
}
const myDog = new Dog("Canine", "Labrador");
console.log(myDog.type); // Outputs: Canine
console.log(myDog.breed); // Outputs: Labrador
myDog.makeSound(); // Outputs: Woof woof
In this example, Dog
is a subclass of Animal
. It inherits the type
property and the makeSound()
method, yet it overrides makeSound()
to define a specific behavior for dogs.
- Super Keyword: The
super
keyword is used to refer to the parent class's constructor, methods, or properties. It helps in calling the superclass constructor during instantiation and invoking superclass methods within subclass methods.
Example:
class Cat extends Animal {
name: string;
constructor(type: string, name: string) {
super(type);
this.name = name;
}
introduce(): void {
console.log(`I am a ${this.type} named ${this.name}`);
super.makeSound(); // Calling the superclass method
}
}
const myCat = new Cat('Feline', 'Whiskers');
myCat.introduce(); // Outputs: I am a Feline named Whiskers
// Some generic sound
In this example, super(type)
calls the Animal
constructor, and super.makeSound()
uses the makeSound
method defined in Animal
.
- Access Modifiers: Access modifiers determine the visibility and accessibility of class members. They include
public
,protected
, andprivate
.public
: The member is accessible from anywhere.protected
: The member is accessible only within its containing class and any classes that derive from it.private
: The member is accessible only within its containing class.
Role of Abstract Classes
Abstract classes serve as a blueprint for other classes. They cannot be instantiated directly and are used to define properties and methods that must be included in any subclass derived from them.
- Abstract Methods: Abstract classes can contain abstract methods, which do not have an implementation and must be implemented by subclasses.
Example:
abstract class Vehicle {
abstract move(): void; // Abstract method
startEngine(): void {
console.log('Engine started');
}
}
class Car extends Vehicle {
move(): void { // Implementation of abstract method
console.log('Car is moving');
}
}
const myCar = new Car();
myCar.startEngine(); // Outputs: Engine started
myCar.move(); // Outputs: Car is moving
// const v = new Vehicle(); // Compile-time error: Cannot create an instance of an abstract class
In the above code, Vehicle
is an abstract class with one abstract method move()
and one concrete method startEngine()
. The Car
class extends Vehicle
and provides an implementation for the move()
method.
- Purpose: Abstract classes are useful when you want to enforce certain behaviors in any derived classes. They centralize common code and provide a template for more specialized classes, promoting design patterns and reducing redundancy.
Important Information
Single Inheritance: Unlike languages that allow multiple inheritance, TypeScript supports single inheritance, meaning each class can inherit from exactly one parent class.
Constructor Function: If a subclass has a constructor, it must call
super()
to initialize the parent class properties.Method Overriding: Subclasses can override methods inherited from their superclass to provide a more specific implementation.
Static Members: Static members, whether they are properties or methods, belong to the class itself rather than instances of the class. Inheritance does not impact static members.
Interfaces: While abstract classes are used more for establishing a hierarchy and sharing common functionality, interfaces are often better suited for defining common functionality across various unrelated classes.
Access Control: Proper use of access modifiers (public, protected, private) is crucial for encapsulating data and preventing unauthorized access or modification.
Performance Consideration: Although inheritance in TypeScript is powerful, overusing it can lead to complex hierarchies and potential performance issues. It’s essential to use inheritance judiciously and consider composition or mixins as alternatives.
Practical Applications
Code Reusability: Inheritance makes code easier to use and develop across different parts of an application, especially in large-scale systems where similar functionalities are required in multiple classes.
Polymorphism: It allows polymorphic behavior, which means a function can operate on objects of different types through common behavior defined by a superclass.
Template Method Pattern: Abstract classes can be used to implement template method patterns, where the skeleton of an algorithm is defined in the superclass but subclasses are allowed to provide steps of the algorithm.
Example:
abstract class Game {
abstract play(): void;
loadAssets() {
console.log('Loading assets...');
}
playGame() {
this.loadAssets();
this.play();
this.scoreDisplay();
}
scoreDisplay() {
console.log('Displaying score...');
}
}
class Chess extends Game {
play(): void {
console.log('Playing chess...');
}
}
const chessGame = new Chess();
chessGame.playGame();
/*
Outputs:
Loading assets...
Playing chess...
Displaying score...
*/
In this example, Chess
class implements the play
method and inherits loadAssets()
and scoreDisplay()
from Game
, allowing for a consistent flow of gameplay without duplicating code.
Understanding and effectively utilizing inheritance and abstract classes in TypeScript enables developers to write more organized, scalable, and maintainable code, aligning with modern software development practices.
Conclusion
Online Code run
Step-by-Step Guide: How to Implement TypeScript Inheritance and Abstract Classes
Example 1: Basic Inheritance
Inheritance allows a class to inherit properties and methods from another class. The class that inherits is called the derived or child class, and the class being inherited from is known as the base or parent class.
Step-by-Step Implementation
- Define a Base Class: Create a
Vehicle
class that has common properties and methods for all types of vehicles. - Define a Derived Class: Create a
Car
class that extends theVehicle
class and adds its own specific features.
// Base Class: Vehicle
class Vehicle {
protected make: string;
protected model: string;
protected year: number;
constructor(make: string, model: string, year: number) {
this.make = make;
this.model = model;
this.year = year;
}
getDetails() {
return `${this.year} ${this.make} ${this.model}`;
}
startEngine() {
console.log("Engine started");
}
stopEngine() {
console.log("Engine stopped");
}
}
// Derived Class: Car
class Car extends Vehicle {
private numberOfDoors: number;
constructor(make: string, model: string, year: number, numberOfDoors: number) {
// Call the constructor of the base class
super(make, model, year);
this.numberOfDoors = numberOfDoors;
}
getCarDetails() {
return `${this.getDetails()} with ${this.numberOfDoors} doors`;
}
}
const myCar = new Car('Toyota', 'Camry', 2022, 4);
console.log(myCar.getCarDetails()); // Output: 2022 Toyota Camry with 4 doors
myCar.startEngine(); // Output: Engine started
myCar.stopEngine(); // Output: Engine stopped
Explanation:
Vehicle
Class: Serves as a base class with protected properties (make
,model
,year
). These are accessible within theVehicle
class and any class that extends it.- Constructor in
Vehicle
: Initializes the properties of a vehicle. - Methods in
Vehicle
: Common functionalities likestartEngine()
andstopEngine()
. Car
Class: Inherits fromVehicle
using theextends
keyword.super
Keyword: Calls the constructor of the base class (Vehicle
), passing necessary parameters.Car
Specific Properties: Adds anumberOfDoors
property specific to cars.getCarDetails
Method: Utilizes the method from the base class (getDetails
) to create a detailed description.
Example 2: Using Access Modifiers
Access modifiers in TypeScript (private
, protected
, public
) control the visibility of class members.
Step-by-Step Implementation
- Define a Base Class
Animal
: It should include private and protected properties. - Define a Derived Class
Dog
: It should extendAnimal
and add specific behaviors.
// Base Class: Animal
class Animal {
private name: string;
protected species: string;
constructor(name: string, species: string) {
this.name = name;
this.species = species;
}
getName() {
return this.name;
}
makeSound() {
console.log(`${this.species} makes a sound`);
}
}
// Derived Class: Dog
class Dog extends Animal {
private breed: string;
constructor(name: string, species: string, breed: string) {
super(name, species);
this.breed = breed;
}
getBreed() {
return this.breed;
}
makeSound() {
super.makeSound();
console.log(`Specifically, ${this.getName()} the ${this.breed} barks`);
}
}
const myDog = new Dog('Buddy', 'Canine', 'Golden Retriever');
console.log(myDog.getBreed()); // Output: Golden Retriever
myDog.makeSound(); // Output: Canine makes a sound
// Specifically, Buddy the Golden Retriever barks
Explanation:
- Private Property (
name
): This property is only accessible within theAnimal
class. - Protected Property (
species
): This property can be accessed within theAnimal
class and any class that extends it (Dog
). - Public Methods (
getName
andmakeSound
):getName
returns the name of the animal, whilemakeSound
logs a generic message. - Overriding Methods: The
Dog
class overrides themakeSound
method to provide species-specific behavior, calling the superclass method usingsuper
.
Example 3: Abstract Classes
An abstract class cannot be instantiated on its own and must be inherited by other classes. Abstract classes often contain one or more abstract methods that do not have an implementation but must be implemented by derived classes.
Step-by-Step Implementation
- Define an Abstract Class
Shape
:- Includes abstract properties that need to be defined by subclasses.
- Includes an abstract method
area
that needs to be implemented.
- Define a Concrete Class
Circle
:- Extends
Shape
and provides an implementation for the abstract properties and method.
- Extends
// Abstract Class: Shape
abstract class Shape {
protected width: number;
protected height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
abstract area(): number;
// Non-abstract method (optional)
getDimensions(): { width: number, height: number } {
return { width: this.width, height: this.height };
}
}
// Concrete Class: Circle
class Circle extends Shape {
// Override the width and height to represent the radius of the circle
private radius: number;
constructor(radius: number) {
super(radius * 2, radius * 2); // width and height are diameter for a circle
this.radius = radius;
}
// Implementing the abstract method
area(): number {
return Math.PI * this.radius * this.radius;
}
}
const myCircle = new Circle(5);
console.log(myCircle.getDimensions()); // Output: { width: 10, height: 10 }
console.log(`Area of circle: ${myCircle.area()}`); // Output: Area of circle: 78.53981633974483
Explanation:
- Abstract Class (
Shape
): Defines template properties (width
,height
) and an abstract method (area
). - Cannot Instantiate
Shape
: Trying to create an instance ofShape
directly would result in an error. - Concrete Derived Class (
Circle
): Inherits fromShape
, initializes properties, and implements the abstract method (area
).
Example 4: Combining Inheritance and Abstract Classes
This example combines concepts from the previous examples by creating an abstract class that can serve as a template for various shapes, each of which can extend and implement the abstract class methods accordingly.
Step-by-Step Implementation
- Define an Abstract Class
Shape
:- Includes abstract method
area()
. - Includes non-abstract method
getDimensions()
.
- Includes abstract method
- Define Derived Classes
Circle
andRectangle
:- Both extend
Shape
and provide implementations for the abstract methodarea()
.
- Both extend
// Abstract Class: Shape
abstract class Shape {
protected width: number;
protected height: number;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
abstract area(): number;
getDimensions(): { width: number, height: number } {
return { width: this.width, height: this.height };
}
}
// Derived Class: Circle
class Circle extends Shape {
private radius: number;
constructor(radius: number) {
super(radius * 2, radius * 2); // Diameter for width and height
this.radius = radius;
}
area(): number {
return Math.PI * this.radius * this.radius;
}
}
// Derived Class: Rectangle
class Rectangle extends Shape {
constructor(width: number, height: number) {
super(width, height);
}
area(): number {
return this.width * this.height;
}
}
const myCircle = new Circle(5);
console.log(myCircle.getDimensions()); // Output: { width: 10, height: 10 }
console.log(`Area of circle: ${myCircle.area()}`); // Output: Area of circle: 78.53981633974483
const myRectangle = new Rectangle(3, 7);
console.log(myRectangle.getDimensions()); // Output: { width: 3, height: 7 }
console.log(`Area of rectangle: ${myRectangle.area()}`); // Output: Area of rectangle: 21
Explanation:
- Abstract Class (
Shape
): Provides a common interface for different shapes. - Derived Class (
Circle
): Implements thearea()
method using the radius property. - Derived Class (
Rectangle
): Implements thearea()
method using the width and height properties.
Example 5: Inherited Constructor Overriding
Sometimes you might want to override the constructor in a derived class to have a different constructor signature or additional behavior. Here's how:
Step-by-Step Implementation
- Define a Base Class
Person
:- Has a constructor initializing
firstName
andlastName
.
- Has a constructor initializing
- Define a Concrete Class
Employee
:- Inherits from
Person
. - Overrides the constructor to also take an
employeeId
.
- Inherits from
Top 10 Interview Questions & Answers on TypeScript Inheritance and Abstract Classes
Top 10 Questions and Answers on TypeScript Inheritance and Abstract Classes
Inheritance is a fundamental concept in TypeScript (and many other languages) that allows a class to inherit properties and methods from a parent class. This promotes code reusability and establishes a clear hierarchy between classes.
Example:
// Base class
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a noise.`);
}
}
// Derived class
class Dog extends Animal {
breed: string;
constructor(name: string, breed: string) {
super(name); // Calls the constructor of the base class
this.breed = breed;
}
speak() {
console.log(`${this.name} barks.`);
}
}
const myDog = new Dog("Buddy", "Golden Retriever");
myDog.speak(); // Outputs: Buddy barks.
2. What is the difference between super
and this
in TypeScript?
this
refers to the current instance of the class.super
is used to invoke the constructor of the parent class, allowing you to access members of the parent class.
3. How do abstract classes work in TypeScript?
Abstract classes are used to provide a base class from which other classes may be derived. They cannot be instantiated directly. An abstract class can contain both abstract methods (which do not contain an implementation and must be implemented in derived classes) and non-abstract methods.
Example:
abstract class Animal {
abstract makeSound(): void;
move(): void {
console.log("Moving along!");
}
}
class Dog extends Animal {
makeSound() {
console.log("Woof! Woof!");
}
}
let myDog = new Dog();
myDog.makeSound(); // Outputs: Woof! Woof!
myDog.move(); // Outputs: Moving along!
4. Can abstract classes have non-abstract methods?
Yes, abstract classes can definitely have non-abstract methods. These methods have an implementation and can be called directly on instances of derived classes.
5. What happens if a derived class does not implement an abstract method from its base abstract class?
If a derived class does not implement an abstract method from its base abstract class, it must also be declared as abstract.
Example:
abstract class Animal {
abstract makeSound(): void;
}
class Dog extends Animal {
// This implementation is missing, so Dog must also be abstract.
}
// const myDog = new Dog(); // Error: Cannot create an instance of an abstract class.
6. Can a class extend multiple classes in TypeScript?
No, TypeScript does not support multiple class inheritance. A class can only extend one base class. However, it can implement multiple interfaces, which can be used to achieve some of the benefits of multiple inheritance.
7. What is the purpose of the protected
modifier in TypeScript, and how does it relate to inheritance?
The protected
modifier in TypeScript allows properties and methods to be accessed only within the class itself and by derived classes, but not from outside the class hierarchy. This is useful for defining internal state that is not exposed to the public but needs to be shared among base and derived classes.
Example:
class Animal {
protected species: string;
constructor(species: string) {
this.species = species;
}
}
class Dog extends Animal {
speak() {
console.log(`The ${this.species} barks.`);
}
}
const myDog = new Dog("Dog");
// myDog.species; // Error: Property 'species' is protected and only accessible within class 'Animal' and its subclasses.
8. What is method overriding in TypeScript?
Method overriding is when a derived class provides a specific implementation for a method that is already defined in its base class. This allows the derived class to modify or extend the functionality of the inherited method.
Example:
class Animal {
speak() {
console.log("Some generic animal sound");
}
}
class Dog extends Animal {
speak() {
console.log("Woof! Woof!");
}
}
const myDog = new Dog();
myDog.speak(); // Outputs: Woof! Woof!
9. Can interfaces be used with abstract classes in TypeScript?
Yes, interfaces can be used with abstract classes. An abstract class can implement an interface, and derived classes must implement any abstract methods defined in the abstract class and any methods defined in the interface.
Example:
interface Speaker {
makeSound(): void;
}
abstract class Animal implements Speaker {
abstract makeSound(): void;
}
class Dog extends Animal {
makeSound() {
console.log("Woof! Woof!");
}
}
const myDog = new Dog();
myDog.makeSound(); // Outputs: Woof! Woof!
10. When should you use abstract classes versus interfaces in TypeScript?
Abstract Classes: Use abstract classes when you need a base class that provides shared implementation, such as methods and properties, that can be used by derived classes. Abstract classes are also useful when you need to prevent the instantiation of a class directly.
Interfaces: Use interfaces when you need to define a contract that can be implemented by any class, regardless of how it is implemented. Interfaces are useful for creating a loosely coupled system where the specific implementations can vary widely. Interfaces are also used for defining the shape of an object without providing any implementation details.
Login to post a comment.