Principles

Last Updated: 10/9/2024

Dependency Inversion Principle

  • High-level modules should not depend on low-level modules. Both should depend on abstraction
  • This means that a particular class should not depend directly on another class, but on an abstraction (interface) of this class.
  • Applying this principle reduces dependency on specific implementations and makes our code more reusable.

Example 1

Problem

internal class GmailClient
{
    public void SendEmail(string recipient, string subject, string body)
    {
        Console.WriteLine("Sending email through gmail");
    }
}
internal class EmailService
{
    GmailClient emailClient = new GmailClient();
    public void SendEmail(string recipient, string subject, string body)
    {
        emailClient.SendEmail(recipient, subject, body);
    }
}
  • The EmailService class directly depends on the GmailClient class, a low-level module that implements the details of sending emails using the Gmail API.
  • To adhere to the DIP, you can introduce an abstraction (interface) for email clients
  • the EmailService class depends on the EmailClient abstraction, and the low-level email client implementations (GmailClient and OutlookClient) depend on the abstraction.

Solution

internal interface IEmailClient
{
    void SendEmail(string recipient, string subject, string body);
}
internal class GmailClient : IEmailClient
{
    public void SendEmail(string recipient, string subject, string body)
    {
        Console.WriteLine("Sending email throug gmail");
    }
}
internal class OutlookClient : IEmailClient
{
    public void SendEmail(string recipient, string subject, string body)
    {
        Console.WriteLine("Sending email throug outlook");
    }
}
internal class EmailService
{
    private readonly IEmailClient client;

    public EmailService(IEmailClient client)
    {
        this.client = client;
    }

    public void SendEmail(string recipient, string subject, string body)
    {
        client.SendEmail(recipient, subject, body);
    }
}
internal class Program
{
    static void Main(string[] args)
    {
        var gmailClient = new GmailClient();
        var emailService = new  EmailService(gmailClient);
        emailService.SendEmail("recipient@gmail.com", "Subject", "Body");
   }
}

Example 2

Problem

public class StudentController
{

    //tight coupling
    private StudentRepository _stdRepo = new StudentRepository();
       
    public void Save(Student s)
    {
        _stdRepo.AddStudent(s);
    }
}

public class StudentRepository 
{
    public void AddStudent(Student std)
    {
        //EF code removed for clarity
    }

    public void DeleteStudent(Student std)
    {
        //EF code removed for clarity
    }

    public void EditStudent(Student std)
    {
        //EF code removed for clarity
    }
        
    public IList<Student> GetAllStudents()
    {
        //EF code removed for clarity
    }
}

Solution

public interface IStudentRepository
{
    void AddStudent(Student std);
    void EditStudent(Student std);
    void DeleteStudent(Student std);
        
    IList<Student> GetAllStudents();
}

public class StudentRepository : IStudentRepository
{
    public void AddStudent(Student std)
    {
        //code removed for clarity
    }

    public void DeleteStudent(Student std)
    {
        //code removed for clarity
    }

    public void EditStudent(Student std)
    {
        //code removed for clarity
    }

    public IList<Student> GetAllStudents()
    {
        //code removed for clarity
    }
}

public class StudentController
{
    public Student(IStudentRepository stdRepo)
    {
        _stdRepo = stdRepo;
    }

    public void Save(Student s)
    {
        _stdRepo.AddStudent(s);
    }
}

  • The StudentRepository class provides the implementation of the methods, so it depends on the methods of the IStudentRepository interface.