Introduction

In this article, we will cover the Dependency Inversion Principle (DIP).

Dependency Inversion Principle (DIP)

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.