Dependency Inversion Principle (DIP)

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();
        }
    }
}

DI

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.