Introduction
In this article, we will cover the Dependency Inversion Principle (DIP).
Dependency Inversion Principle (DIP)
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend upon details. Details should depend on abstractions.
- This can be hard to understand at first, but if you've worked with .NET/.NET Core framework, you've seen an implementation of this principle in the form of Dependency Injection (DI). While they are not identical concepts, DIP keeps high-level modules from knowing the details of their low-level modules and setting them up. It can accomplish this through DI. A huge benefit of this is that it reduces the coupling between modules. Coupling is a very bad development pattern because it makes your code hard to refactor.
Let’s take an example of User Authentication.
Suppose we’re building a system that allows users to authenticate. Initially, users authenticate using a username and password system stored in a local database. However, you anticipate that in the future, we may wish to allow users to authenticate using third-party services like Google, Facebook, or even biometrics.
Violation of DI/Bad Code
using System;
namespace DesignPattern
{
public class DatabaseAuthenticator
{
public bool Authenticate(string username, string password)
{
// Logic to authenticate using a database
Console.WriteLine($"Authenticating {username} using database.");
return true;
}
}
public class AuthenticationService
{
private DatabaseAuthenticator _authenticator = new DatabaseAuthenticator();
public bool AuthenticateUser(string username, string password)
{
return _authenticator.Authenticate(username, password);
}
}
}
Note. This design tightly couples the AuthenticationService to the DatabaseAuthenticator.
Using DI / Good Code
using System;
namespace DIPDemo
{
//Define the Interface
public interface IAuthenticator
{
bool Authenticate(string identifier, string credential);
}
//Concrete implementations
public class DatabaseAuthenticator : IAuthenticator
{
public bool Authenticate(string username, string password)
{
// Logic to authenticate using a database
Console.WriteLine($"Authenticating {username} using database.");
return true;
}
}
public class GoogleAuthenticator : IAuthenticator
{
public bool Authenticate(string token, string _)
{
// Logic to authenticate using Google
Console.WriteLine($"Authenticating using Google with token: {token}");
return true;
}
}
//The AuthenticationService class will now depend on the abstraction
public class AuthenticationService
{
private readonly IAuthenticator _authenticator;
public AuthenticationService(IAuthenticator authenticator)
{
_authenticator = authenticator;
}
public bool AuthenticateUser(string identifier, string credential)
{
return _authenticator.Authenticate(identifier, credential);
}
}
//Testing the Dependency Inversion Principle
public class Program
{
public static void Main()
{
var dbAuthenticator = new DatabaseAuthenticator();
var authServiceWithDB = new AuthenticationService(dbAuthenticator);
authServiceWithDB.AuthenticateUser("vyelve", "password123$");
var googleAuthenticator = new GoogleAuthenticator();
var authServiceWithGoogle = new AuthenticationService(googleAuthenticator);
authServiceWithGoogle.AuthenticateUser("OAuthTokenHere", "");
Console.ReadKey();
}
}
}
Note. Here, the AuthenticationService is decoupled from any specific authentication method. This allows for easy addition of new methods or changes to existing ones without affecting the central AuthenticationService.