What is the Dependency Injection Design Pattern?

Göksu Deniz
6 min readJan 7, 2023

--

Image is created by DALL-E 2 AI

Dependency injection is a design pattern in which a class receives its dependencies from external sources rather than creating them itself. This helps to reduce the tight coupling between classes, making it easier to change the implementation of a dependency without affecting the class that uses it.

Dependency injection is a programming technique that involves providing a class with its dependencies from the outside, rather than having the class create them itself. This allows the class to be more flexible and easier to test, since the dependencies can be easily swapped out for different implementations if needed.

Imagine you have a toy robot that you want to play with. The toy robot needs a certain type of battery to work. You could give the toy robot the battery it needs by putting the battery inside the toy robot every time you want to play with it.

But what if you wanted to use a different type of battery? You would have to open up the toy robot and replace the battery, which would be a lot of work.

Instead, you could design the toy robot so that it has a special place to put the battery, called a “battery compartment.” Then, you could just open the battery compartment and insert any type of battery you want. This would make it much easier to change the battery if you want to use a different one.

Dependency injection is a way of writing code that works like the toy robot with the battery compartment. Instead of a toy robot, you have a piece of code that needs something to work properly, like a library or a service. Instead of putting that thing directly into the code, you put it in a special place where it can be easily changed. This makes it easier to modify the code and use it in different ways.

In software development, dependencies are pieces of code that are used by other pieces of code. For example, if you are writing a program to do math calculations, you might use a library that has code to perform basic math operations like addition and subtraction. The library would be a dependency of your program.

The dependency injection design pattern is a way of managing dependencies in code. It involves creating a “injector” component that is responsible for providing all the dependencies that are needed by other parts of the code.

Here’s how it works:

  1. The injector component is given a list of the dependencies that are needed by the rest of the code.
  2. The injector component creates or retrieves the dependencies and stores them in a way that they can be accessed by other parts of the code.
  3. When a piece of code needs a dependency, it asks the injector component for the dependency instead of creating or retrieving it itself.

If you need to use a different version of a dependency, you can simply update the injector component to provide the new version, rather than having to change the code that uses the dependency.

Here is IMathOperation interface and two of it’s concretes.

public interface IMathOperation
{
int Calculate(int x, int y);
}

public class AdditionOperation : IMathOperation
{
public int Calculate(int x, int y)
{
return x + y;
}
}

public class SubtractionOperation : IMathOperation
{
public int Calculate(int x, int y)
{
return x - y;
}
}

If you don’t use dependency injection design pattern, your code looks like this:

public class Calculator
{
private readonly IMathOperation _mathOperation;

public Calculator()
{
_mathOperation = new AdditionOperation();
}

public int Calculate(int x, int y)
{
return _mathOperation.Calculate(x, y);
}
}

In this version of the Calculator class, the IMathOperation dependency is created directly within the Calculator class, rather than being injected from the outside. This means that if you want to use a different math operation like SubtractionOperation, you would have to modify the Calculator class itself, rather than being able to inject a different implementation.

Using the dependency injection design pattern allows you to change the dependencies of a class without having to modify the class itself.

Here is how you use dependency injection design pattern in Calculator class:

public class Calculator
{
private readonly IMathOperation _mathOperation;

public Calculator(IMathOperation mathOperation)
{
_mathOperation = mathOperation;
}

public int Calculate(int x, int y)
{
return _mathOperation.Calculate(x, y);
}
}

In this example, the Calculator class has a dependency on the IMathOperation interface. The Calculator class is given an IMathOperation implementation (either AdditionOperation or SubtractionOperation) when it is constructed, using dependency injection. This allows the Calculator class to be flexible and easy to modify, because the specific math operation it uses can be changed simply by injecting a different IMathOperation implementation.

Finally, let’s use the Calculator class and inject the IMathOperation dependency:

var calculator = new Calculator(new AdditionOperation());
int result = calculator.Calculate(2, 3);
Console.WriteLine(result); // Outputs: 5

And some other where of your project you can use the same Calculator class with different math operation like this below:

var calculator = new Calculator(new SubstructionOperation());
int result = calculator.Calculate(2, 3);
Console.WriteLine(result); // Outputs: -1

In .NET you can use the Startup class in an ASP.NET Core application to configure dependency injection. The Startup class is where you can specify which services should be available for dependency injection, and how they should be created.

For example, here is a simple Startup class that configures dependency injection for an IMathOperation service:

public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IMathOperation, AdditionOperation>();
}
}

This code tells the dependency injection system to use the AdditionOperation class whenever an IMathOperation service is requested.

You can then use dependency injection to inject the IMathOperation service into your controllers and other components:

public class CalculatorController : Controller
{
private readonly IMathOperation _mathOperation;

public CalculatorController(IMathOperation mathOperation)
{
_mathOperation = mathOperation;
}

// Action methods go here...
}

When the CalculatorController is constructed, the dependency injection system will use the AdditionOperation class to create an IMathOperation instance and inject it into the controller.

There are several different ways to implement dependency injection, including:

1. Constructor injection: This is the most common form of dependency injection, and involves injecting the dependencies into a class via its constructor.

public class Calculator
{
private readonly IMathOperation _mathOperation;

public Calculator(IMathOperation mathOperation)
{
_mathOperation = mathOperation;
}

// ...
}

2. Property injection: This involves injecting dependencies into a class via a public property.

public class Calculator
{
public IMathOperation MathOperation { get; set; }

// ...
}

3. Method injection: This involves injecting dependencies into a class via a method.

public class Calculator
{
private readonly IMathOperation _mathOperation;

public void SetMathOperation(IMathOperation mathOperation)
{
_mathOperation = mathOperation;
}

// ...
}

4. Interface injection: This involves defining a special interface that contains methods for injecting dependencies.

public interface ICalculator
{
void SetMathOperation(IMathOperation mathOperation);
}

public class Calculator : ICalculator
{
private readonly IMathOperation _mathOperation;

public void SetMathOperation(IMathOperation mathOperation)
{
_mathOperation = mathOperation;
}

// ...
}

These are the most common types of dependency injection. Which one you choose will depend on your specific needs and preferences.

Conclusion

The dependency injection design pattern is a way of managing dependencies in code that can increase flexibility and maintainability. It involves creating an injector component that is responsible for providing the dependencies that are needed by other parts of the code.

Using dependency injection can have some advantages, such as making it easier to modify and reuse code, and making it easier to test. However, it can also add an extra layer of complexity and potentially have a small performance overhead.

Whether or not to use dependency injection in your application will depend on your specific needs and goals. It can be a useful tool in many situations, but it’s important to carefully consider the trade-offs before deciding to use it.

But most of scenarios, you may encounter some problems when you do not use it. So I prefer that to use.

Thanks for reading! If you found the article helpful, you can clap and follow. So you will notified of new articles.

# Reference

It was created with the help of ChatGPT AI.

--

--

Göksu Deniz

Software Engineer, passionate about creating efficient solutions. Skilled in mentoring teams to deliver successful projects. Always exploring new tech trends.