Multiple Inheritance of Interface in TypeScript

Updated: January 7, 2024 By: Guest Contributor Post a comment

Introduction

In object-oriented programming, inheritance allows new classes to take on the properties and methods of existing classes. TypeScript, as a superset of JavaScript, expands on JavaScript’s capabilities by adding static types and a powerful type system, which includes interfaces. These interfaces can be utilized to describe the shape that objects should have. One of the incredible features of TypeScript is the ability to inherit from multiple interfaces, providing a way to create structured and maintainable code.

Basic Example of Multiple Interface Inheritance

Let’s start with a basic example to illustrate multiple inheritance with interfaces:

interface Runnable {
  run(): void;
}

interface Swimmable {
  swim(): void;
}

interface Athlete extends Runnable, Swimmable {
  compete(): void;
}

class Triathlete implements Athlete {
  run() {
    // Implement running functionality
  }
  swim() {
    // Implement swimming functionality
  }
  compete() {
    // Implement competing functionality
  }
}

In the above example, the Athlete interface extends two other interfaces, Runnable and Swimmable. The Triathlete class then implements the Athlete interface, which means it must provide implementation for all methods defined by the Athlete interface as well as the Runnable and Swimmable interfaces.

Advanced Multiple Interface Inheritance Patterns

Now let us look at a more advanced example:

interface Flyable {
  fly(): void;
}

interface Teleporter {
  teleport(destination: string): void;
}

interface Superhero extends Runnable, Flyable, Teleporter {
  saveTheWorld(): void;
}

class SuperSoldier implements Superhero {
  run() {
    // Super speed running
  }
  fly() {
    // Soaring through the skies
  }
  teleport(destination: string) {
    // Instant travel
  }
  saveTheWorld() {
    // Heroic deeds
  }
}

The Superhero interface is a combination of three interfaces—Runnable, Flyable, and Teleporter—which define a broader set of abilities. A super soldier class implementing Superhero must then provide concrete methods for all abilities.

Interface Segregation and Composition

Beyond simple examples of inheritance, TypeScript interfaces allow for a clear implementation of the Interface Segregation Principle, which is a component of SOLID principles for object-oriented design. This principle suggests that no client should be forced to depend upon interfaces it does not use.

Example of Interface Segregation:

interface Aquatic {
  swim(): void;
  breatheUnderwater(): void;
}

interface Terrestrial {
  walk(): void;
  breatheAir(): void;
}

interface Amphibian extends Aquatic, Terrestrial {
  switchEnvironment(): void;
}

class Frog implements Amphibian {
  swim() {
    // Swimming in water
  }
  breatheUnderwater() {
    // Breathing underwater
  }
  walk() {
    // Walking on land
  }
  breatheAir() {
    // Breathing out of water
  }
  switchEnvironment() {
    // Switching from land to water
  }
}

This segregation allows an implementation class like Frog to only implement interfaces relevant to it, enhancing code maintainability and understandability.

Combining Interfaces with Union Types and Intersection Types

In some cases, you may want to combine interfaces in a more flexible way without creating a new interface.

Intersection Types:

type Motion = Runnable & Swimmable;

function compete(competitor: Motion) {
  competitor.run();
  competitor.swim();
}

By utilizing TypeScript’s intersection types, we can define a type that combines Runnable and Swimmable without the need for an explicit interface that extends them.

Union Types:

type Actor = Runnable | Swimmable;

function getMoving(mover: Actor) {
  if ('run' in mover) {
    mover.run();
  } else if ('swim' in mover) {
    mover.swim();
  }
}

Union types, on the other hand, allow an object to be one type or another. In this case, a mover can either run or swim.

Caveats and Best Practices

While multiple interface inheritance allows for flexible designs, it also comes with caveats. Avoid creating overly complex structures that can be hard to understand or maintain. Consider using combination techniques like intersection and union types where needing full interface implementations is not necessary.

Conclusion

Multiple inheritance of interfaces in TypeScript enables developers to architect software with clean, modular, and reusable components. With its advanced type system, TypeScript provides a plethora of ways to apply interface inheritance, which, when used wisely, leads to better code organization and easier maintenance. Reflecting on best practices and principles like interface segregation can further refine the use of these features to produce well-structured applications.