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.