You are currently viewing Achieve loose coupling through Dependency Injection

Achieve loose coupling through Dependency Injection

What is tight or high coupling

In order to understand tight or high coupling, we need to understand “Dependency”. Dependency is an essential component, service, library, or piece of code that is required by another component of the system to work. 

In programming, if components have a high level of dependency on each other, that is called tight coupling. In simple words, too much dependency is called tight coupling.

Why we need to avoid tight coupling?

We tend to avoid tight coupling due to the ripple effect or domino effects because if we have tight coupling, then changing in one component will impact many other components and thus it will be difficult to maintain code. So that is the main reason, that we need to avoid the tight coupling.

Loose coupling

As software engineers, we aim to have loose coupling as much as possible. Loose coupling or low coupling is the opposite of high coupling. There are specific benefits that we get by having loose coupling inside our program. In loose coupling, components communicate with each other through well-defined interfaces and have very little knowledge about the internals of each other. This reduces the ripple or domino effects described in tight coupling.

What is Dependency Injection?

It is a design pattern used in software development to produce loosely coupled code. In this technique, classes do not create their dependency object, instead, these objects are injected from outside, hence the term “dependency injection”.

Hands on

Now, we know the very basics of tight and loose coupling, it is time to understand it clearly by doing a hands-on exercise. In this hands-on, I will be performing the following tasks.

  • I will write a code for tight coupling
  • I will do a two-step modification in the code to remove the tight coupling through dependency injection
    • Through dependency injection using a concrete class
    • Through dependency injection using the interface
  • In the final step, I will use IoC containers to manage the Dependency injection.

Code with Tigh Coupling

Create a new console application. I named my console application “dependency-injection”.

Add two classes to this project with following names:-

  • StudentService,cs
  • ResultService.cs
Added two Files

Code of the ResultService.cs class is given below:-

public class ResultService
{
    public void GetResult(int studentId)
    {
        Random marks = new Random();
        int result = marks.Next(50,100);
        Console.WriteLine(string.Format("Student haveing Id: {0} obtained total marks {1} out of 100", studentId,result ));
    }
}

The code of the StudentService class is given below:-

public class StudentService
{
    private readonly ResultService _resultService;
    public StudentService()
    {
        // The following line is creating the object of the Result Service, It is tight coupling
        _resultService = new ResultService();
    }
    public void GetStudentResult()
    {
        _resultService.GetResult(10);
    }
}

The code of the Program.cs file is given below:-

// See https://aka.ms/new-console-template for more information
StudentService _studentService = new StudentService();
_studentService.GetStudentResult();

Output

shahid@shahid-VirtualBox:~/repos/Dependency-Injection$ dotnet run
Student haveing Id: 10 obtained total marks 53 out of 100
shahid@shahid-VirtualBox:~/repos/Dependency-Injection$ 

Upon executing the console application, It can be seen that the output is shown on the terminal showing the marks obtained against the provided Student ID.

In this example, I created the object of the ResultService directly in the constructor of the Student Service. This is a classical example of tight coupling.

Remove Tight Coupling through Dependency Injection

Now, I will modify the code and will remove tight coupling step by step. In first step, I will remove the code that is creating the object of the ResultService inside the Student Service. 

Step 1 - Remove Object Creation Inside the StudentService and Inject Dependency through constructor

public class StudentService
{
    private readonly ResultService _resultService;
    public StudentService(ResultService resultService)
    {
        // The following line is creating the object of the Result Service, It is tight coupling
        this._resultService = resultService;
    }
    public void GetStudentResult()
    {
        _resultService.GetResult(10);
    }
}

In the above code, a few modifications are done and those are given below:-

The constructor of StudentService is now accepting the object of the “ResultService” as an argument. This object creation is not done at the “StudentService” level. Instead, the created object is passed to the StudentService as an argument and is assigned to the internal instance of the ResultService in the constructor as shown in line # 7. 

Since we have modified the “StudentService”, so we need to modify our main program where we are creating the StudentService. This modified code is also given below:-

 

// See https://aka.ms/new-console-template for more information


ResultService resultService = new ResultService();
StudentService _studentService = new StudentService(resultService);
_studentService.GetStudentResult();

In the above code, I have created the object of the “ResultService” in the main program and passed it as an argument while creating the object of the “StudentService”. Now Build and run the project and you will see a similar response as we were getting before removing the tight coupling.

Step 2 - Enhancing Dependency Injection using Interfaces

In step 1, I removed the object creation from inside the ResultService class and I did it because dependency injection states

classes do not create their dependency object, instead, dependent objects are injected from the outside.

But if we recall the “Loose coupling”, it also states that in the loose coupling, the components communicate with each other through interfaces. So in order to enhance our Dependency injection, we need to do some code refactoring, and for that, I will write an Interface for ResultService. Its code is given below:-

public interface IResultService
{
    public void GetResult(int studentId);
}

Now, I will modify my ResultService class such that it will implement the IResultService interface. Its updated code is given below:-

public class ResultService : IResultService
{
    public void GetResult(int studentId)
    {
        Random marks = new Random();
        int result = marks.Next(50,100);
        Console.WriteLine(string.Format("Student haveing Id: {0} obtained total marks {1} out of 100", studentId,result ));
    }
}

Now, I will modify the code of the StudentService, such that in the constructor, I will accept the “IResultService”, instead of ResultService, as shown below:-

public class StudentService
{
    private readonly IResultService _resultService;
    public StudentService(IResultService resultService)
    {
        // The following line is creating the object of the Result Service, It is tight coupling
        this._resultService = resultService;
    }
    public void GetStudentResult()
    {
        _resultService.GetResult(10);
    }
}

In the above code, I have made a few changes i.e. In the constructor, I have changed the parameter from “ResultService” to “IResultService”. 

The above change is a step towards achieving the loose coupling because now our StudentService does not strongly depends on the “ResultService”. In fact, now it can accept anything which implements the “IResultService”. This actually means that now we can replace or mock the “ResultService”, without even changing anything in the StudentService.

Now, I will modify the code of the main program, and its updated code is given below:-

IResultService resultService = new ResultService();
StudentService _studentService = new StudentService(resultService);
_studentService.GetStudentResult();

In the above code, I have created the object of ResultService and held it within the object of the interface (upcasting). I did it to achieve polymorphism and decoupling. Now let’s build and run the project and the following is the output.

shahid@shahid-VirtualBox:~/repos/Dependency-Injection$ dotnet run
Student haveing Id: 10 obtained total marks 62 out of 100

POC - Remove ResultService from StudentService without changing anything in StudentService

After doing DI, I claimed that now, we can remove ResultService from the StudentService, without changing anything in the StudentService. In order to prove it, I will create another class and the only constraint while creating that class will be that I will be implementing the IResultService interface. This newly created class is given below:-

public class TestResultService : IResultService
{
    public void GetResult(int studentId)
    {
        Console.WriteLine(String.Format("I am NOT ResultingService, and this student has 55 marks"));
    }
}

I can have as many classes as I need with the only constraint that those classes should be implementing the IResultService interface, and any of these classes can replace the ResultService without changing anything in the StudentService class. To test it, I will make the following modification to the main program:-

IResultService resultService = new TestResultService();
StudentService _studentService = new StudentService(resultService);
_studentService.GetStudentResult();

In the main program, I have upcasted the “TestResultService”, instead of “ResultService” and everything else remains intact. Now build and run the program and you will see the following output:-

shahid@shahid-VirtualBox:~/repos/Dependency-Injection$ dotnet run
I am NOT ResultingService, and this student has 55 marks

Final Step - Use IoC container to manage dependencies

To finalize our example of “Dependency Injection”, I will use the “Inversion of Control” to manage dependencies. In the example, you might have seen that I am declaring the objects in the main program and passing it to the StudentService class. This is called manual dependency injection as I am not using any framework. 

Now I will enhance my code further and will induct IoC. IoC stands for Inversion of Control, and it is a component in your program that manages the creation, resolution, and lifetime of an object. This is the fundamental part of the IoC principle where the creation and management of the object are inverted from the objects to the external container or framework. 

Some IoC Containers

There are many IoC containers available. Some of them are open source and some are for commercial purposes. Microsoft also provides a DI framework to achieve IoC by using Microsoft.Extensions.DependencyInjection. Following are some of the IoC frameworks which we can use in our application:-

In our example, I will be using Ninject. You can find the Ninject on Nuget gallery. Search for Ninject on the Nuget gallery and select the package as shown below:-
NInject IoC conatiner

You can see that it is an IoC DI and it is a lightning-fast, ultra-lightweight dependency injector, making it ideal for our example. Use the following command in the terminal to add this package into your project:-

Implement IoC (Ninject)

dotnet add package Ninject --version 4.0.0-beta.1

Running the above command will add the Ninject package to your project. Please use the version as per your need. I used the same command which is mentioned in the Nuget gallery. You will see output similar to the following once you run this command.

Now, I will modify my main program to use Ninject. For that purpose, I will write code that will basically configure Ninject. Basically, I will be doing the following:-

  • Import Ninject
  • We need to tell Ninject which class to create when a particular Interface is encountered. i.e. In our case, we will tell that use ResultService or TestResultService when the program encounters the IResultService interface.
  • We also need to tell Ninject about other classes which we use in our program. e.g. In our case, I will tell inject to Bind the StudentService to itself when it encounters the StudentService
The complete code is given below:-
// See https://aka.ms/new-console-template for more information
using Ninject;    


IKernel kernel = new StandardKernel();
kernel.Bind<IResultService>().To<ResultService>();
kernel.Bind<StudentService>().ToSelf();

var studentService = kernel.Get<StudentService>();
studentService.GetStudentResult();

// IResultService resultService = new TestResultService();
// StudentService _studentService = new StudentService(resultService);
// _studentService.GetStudentResult();

Now build and run the project and you will see the following output.

Output

Summary

In this hands-on I have covered the following:-

  • What is Tight and loose coupling
  • Why do we need to avoid tight coupling
  • How to modify the code to remove the tight coupling and achieve loose coupling
  • Achieve loose coupling through dependency Injection
  • Achieve loose coupling through manual dependency injection
  • Achieve loose coupling through IoC DI

 

 

Source Code

The entire code is uploaded on GitHub. You can download it by clicking the following:-
Download Source Code from github

This Post Has One Comment

  1. Muhammad Awais Yaqub

    Awesome Article..

Leave a Reply