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:
Feature | Abstract Classes | Interfaces |
---|---|---|
Instantiation | Cannot be instantiated directly | Cannot be instantiated directly |
Methods | Can contain both fully implemented methods and abstract methods | Can only declare method signatures (no implementations) |
Constructor | Can have a constructor | Cannot have a constructor |
Multiple Inheritance | Can only inherit from one class | Can implement multiple interfaces |
Use Case | Use when sharing behavior and defining base functionality | Use 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 (likeDog
orCat
) implement their own version ofmakeSound()
. - 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
, andAirplane
, all of which can implement aDriveable
interface to ensure that each class has adrive()
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.