Skip to main content

Deep Dive Into Dependency Inversion Principle in TypeScript

"High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions."

Robert C. Martin
TypeScript Course Instructor Image
TypeScript Course Instructor Image

Time To Transition From JavaScript To TypeScript

Level Up Your TypeScript And Object Oriented Programming Skills. The only complete TypeScript course on the marketplace you building TypeScript apps like a PRO.

SEE COURSE DETAILS

The Principle

The Dependency Inversion Principle (DIP) is the last principle in the SOLID acronym, which is a collection of five design principles aimed at making software designs more understandable, flexible, and maintainable. Here is a literal definition of the Dependency Inversion Principle:

This principle is primarily concerned with reducing dependencies among the code modules, which leads to more decoupled and easily maintainable systems.

Let's break it down a bit further:

  1. High-level modules should not depend on low-level modules. Both should depend on abstractions: This is suggesting that the high-level modules ( modules that implement business logic or use cases) should not directly depend on or interact with the low-level modules (modules that perform basic, low-level functions like writing to a database or handling HTTP requests). Both should interact through abstractions (like interfaces or abstract classes).

  2. Abstractions should not depend on details. Details should depend on abstractions: This means the abstraction does not know about the underlying implementation. It's the responsibility of the underlying detail (i.e., the classes implementing the interface) to adhere to the contract defined by the abstraction.

Consider the following example in TypeScript:

Without Dependency Inversion:

class MySQLDatabase {
save(data: string): void {
// logic to save data to a MySQL database
}
}

class HighLevelModule {
private database: MySQLDatabase;

constructor() {
this.database = new MySQLDatabase();
}

execute(data: string): void {
// high-level logic
this.database.save(data);
}
}

In the above example, HighLevelModule is a high-level module, and it's directly dependent on the low-level module MySQLDatabase. This means if you decided to change your database from MySQL to MongoDB, you would have to modify HighLevelModule, which is not good.

With Dependency Inversion:

interface IDatabase {
save(data: string): void;
}

class MySQLDatabase implements IDatabase {
save(data: string): void {
// logic to save data to a MySQL database
}
}

class MongoDBDatabase implements IDatabase {
save(data: string): void {
// logic to save data to a MongoDB database
}
}

class HighLevelModule {
private database: IDatabase;

constructor(database: IDatabase) {
this.database = database;
}

execute(data: string): void {
// high-level logic
this.database.save(data);
}
}

In this example, HighLevelModule is now depending on the abstraction IDatabase. Whether the underlying database is MySQL or MongoDB, it doesn't care. It just knows that it can call the save method on the database object. This design allows us to change the database without having to modify the HighLevelModule. This is the Dependency Inversion Principle in action.

Here is how you might instantiate HighLevelModule with different types of databases.

// Instantiate the HighLevelModule with a MySQL database.
let mySQLDatabase: IDatabase = new MySQLDatabase();
let highLevelModule1: HighLevelModule = new HighLevelModule(mySQLDatabase);

// Now use the module to execute some high level function.
highLevelModule1.execute("Some Data for MySQL");

// Instantiate the HighLevelModule with a MongoDB database.
let mongoDBDatabase: IDatabase = new MongoDBDatabase();
let highLevelModule2: HighLevelModule = new HighLevelModule(mongoDBDatabase);

// Now use the module to execute some high level function.
highLevelModule2.execute("Some Data for MongoDB");

In the above example, you can see how we can switch out the database dependency without changing the HighLevelModule code. First, we use a MySQLDatabase, then later a MongoDBDatabase. This is an excellent illustration of the power and flexibility provided by the Dependency Inversion Principle.

What Can You Do Next 🙏😊

If you liked the article, consider subscribing to Cloudaffle, my YouTube Channel, where I keep posting in-depth tutorials and all edutainment stuff for software developers.

YouTube @cloudaffle