Abstract Classes and Interface Implementation in TypeScript

Table of Contents

  • Introduction
  • What is an Abstract Class?
    • Defining an Abstract Class
    • Abstract Methods in TypeScript
    • When to Use Abstract Classes
  • What is an Interface?
    • Defining an Interface
    • Implementing an Interface
    • When to Use Interfaces
  • Comparing Abstract Classes and Interfaces
    • Key Differences
    • When to Choose Each One
  • Practical Examples
    • Example 1: Abstract Class
    • Example 2: Interface Implementation
  • Conclusion

Introduction

In TypeScript, both abstract classes and interfaces are essential tools for defining and enforcing structures and behaviors for objects. While they share some similarities, they have different use cases and provide distinct features. Understanding the differences between abstract classes and interfaces is crucial for building maintainable and scalable TypeScript applications.

This article dives deep into abstract classes and interfaces, explaining how they work in TypeScript, their differences, and when to use each. By the end, you’ll have a clear understanding of how to implement and choose between abstract classes and interfaces in your TypeScript code.


What is an Abstract Class?

An abstract class is a class that cannot be instantiated on its own. It serves as a blueprint for other classes. Abstract classes are meant to be extended by other classes, and they allow you to define both fully implemented methods and methods that must be implemented by subclasses.

Defining an Abstract Class

In TypeScript, you define an abstract class using the abstract keyword. An abstract class can contain both abstract methods (without implementations) and regular methods (with implementations).

abstract class Animal {
// Regular method
move(): void {
console.log("Moving...");
}

// Abstract method
abstract makeSound(): void;
}

Abstract Methods in TypeScript

Abstract methods are methods that don’t have a body and must be implemented by subclasses. These methods are defined using the abstract keyword, and they provide the required signature without implementation.

abstract class Animal {
abstract makeSound(): void;
}

When to Use Abstract Classes

Abstract classes are typically used when:

  • You want to provide shared behavior (methods) across multiple subclasses.
  • You want to enforce that certain methods must be implemented in subclasses (via abstract methods).
  • You need to define default behavior that can be shared across child classes, while still allowing some flexibility for customization.

An abstract class is particularly useful when the base class itself should not be instantiated, but you want to provide common functionality to derived classes.


What is an Interface?

An interface defines a contract for a class or object. It is a blueprint that ensures any class or object that implements it follows the defined structure. Unlike abstract classes, interfaces can only define method and property signatures and cannot contain any implementation.

Defining an Interface

In TypeScript, interfaces are defined using the interface keyword. Interfaces allow you to define properties and methods that any implementing class or object must provide.

interface Animal {
move(): void;
makeSound(): void;
}

Implementing an Interface

A class implements an interface by using the implements keyword. The class must then provide concrete implementations for all the methods defined in the interface.

class Dog implements Animal {
move(): void {
console.log("Dog is moving");
}

makeSound(): void {
console.log("Woof");
}
}

When to Use Interfaces

Interfaces are used when:

  • You want to define a contract for a class to follow, ensuring that the class implements specific methods and properties.
  • You need to allow multiple types to share a common structure without enforcing a strict inheritance chain (since a class can implement multiple interfaces).
  • You want flexibility and the ability to define common behavior across various classes without enforcing shared code or a class hierarchy.

Interfaces are ideal when you are concerned with ensuring that different classes share the same structure and behavior, regardless of where they exist in the class hierarchy.


Comparing Abstract Classes and Interfaces

Key Differences

Here’s a breakdown of the key differences between abstract classes and interfaces:

FeatureAbstract ClassesInterfaces
InstantiationCannot be instantiated directlyCannot be instantiated directly
MethodsCan contain both fully implemented methods and abstract methodsCan only declare method signatures (no implementations)
ConstructorCan have a constructorCannot have a constructor
Multiple InheritanceCan only inherit from one classCan implement multiple interfaces
Use CaseUse when sharing behavior and defining base functionalityUse for defining contracts (structure) to be followed by implementing classes

When to Choose Each One

  • Abstract Class: Choose an abstract class when you need to share code and functionality among multiple subclasses and when you have a common base class that should not be instantiated directly. Example Use Case: If you’re creating a framework for animals and want all animals to have some shared behavior like move(), but want to enforce that specific animals (like Dog or Cat) implement their own version of makeSound().
  • Interface: Choose an interface when you want to define a contract without enforcing implementation details. Interfaces are great when multiple classes need to share the same structure but may implement the behavior in different ways. Example Use Case: If you have different classes like Car, Boat, and Airplane, all of which can implement a Driveable interface to ensure that each class has a drive() method, but you don’t care how each class implements the method.

Practical Examples

Example 1: Abstract Class

Let’s create a base class for different types of Shape, where common behavior like getArea() is shared, but the exact implementation of getArea() differs based on the type of shape.

abstract class Shape {
abstract getArea(): number;

displayArea(): void {
console.log(`Area: ${this.getArea()}`);
}
}

class Circle extends Shape {
radius: number;

constructor(radius: number) {
super();
this.radius = radius;
}

getArea(): number {
return Math.PI * this.radius * this.radius;
}
}

class Square extends Shape {
side: number;

constructor(side: number) {
super();
this.side = side;
}

getArea(): number {
return this.side * this.side;
}
}

// Usage
const circle = new Circle(5);
circle.displayArea(); // Area: 78.53981633974483

const square = new Square(4);
square.displayArea(); // Area: 16

Here, the abstract class Shape defines a contract for all shapes to calculate their area, while the subclasses provide specific implementations for different shape types.

Example 2: Interface Implementation

In this example, we define an interface for a Driveable object and implement it in different classes:

interface Driveable {
drive(): void;
}

class Car implements Driveable {
drive(): void {
console.log("Car is driving");
}
}

class Truck implements Driveable {
drive(): void {
console.log("Truck is driving");
}
}

// Usage
const car = new Car();
car.drive(); // Car is driving

const truck = new Truck();
truck.drive(); // Truck is driving

In this case, the Driveable interface ensures that both Car and Truck implement the drive() method. The exact behavior of the drive() method can vary between different classes.


Conclusion

In TypeScript, abstract classes and interfaces are powerful tools for structuring and enforcing object-oriented designs. Abstract classes allow you to define shared behavior and ensure consistency across derived classes, while interfaces provide a flexible contract for ensuring that different classes implement specific methods.

When choosing between abstract classes and interfaces, consider whether you need shared behavior (abstract class) or just a common structure (interface). By understanding their differences and use cases, you can make more informed design decisions and write more maintainable, scalable code.