Diogo Lewis Mesquita
Technical Team Lead | Cegid | Porto, 🇵🇹
What Is and What Is Not Dependency Injection
Have you ever been asked a question in an interview that made you pause and reflect? I recently found myself in such a situation during an interview for the role of a Senior Software Engineer.
“So tell me, what is Dependency Injection (DI)?”
Dependency Injection it’s a fundamental concept in software development, yet articulating a precise and clear answer can sometimes be challenging. In this blog post, we’ll dive into Dependency Injection, explore what it is and what it isn’t, and try to provide an ideal answer in the context of an interview.
Dependency Injection: A 5-Cent Concept with a 25-Dollar Term
Dependency Injection, often abbreviated as DI, is a design pattern and technique that embodies the principle of Inversion of Control (IoC). IoC is all about changing the flow of control from your code to an external component or framework, which can lead to more maintainable and flexible software. But let’s not get lost in jargon; as James Shore aptly puts it, “Dependency Injection” is a “25-dollar term for a 5-cent concept.”
The core idea of Dependency Injection can be distilled into a simple statement: “Dependency injection means giving an object its instance variables.” This quote encapsulates the essence of DI. Instead of an object creating its own dependencies, they are provided or “injected” from the outside. This concept promotes loose coupling between components and enhances testability.
To explore Dependency Injection further, let’s delve into some practical examples using C#.
Dependency Injection in C# - What It Is
Let’s start with an example that demonstrates Dependency Injection:
// Define an interface representing a dependency.
public interface ILogger
{
void Log(string message);
}
// Create a concrete implementation of the dependency.
public class ConsoleLogger : ILogger
{
public void Log(string message)
{
Console.WriteLine(message);
}
}
// Class that depends on ILogger through constructor injection.
public class UserService
{
private readonly ILogger logger;
public UserService(ILogger logger)
{
this.logger = logger;
}
public void DoSomething()
{
// Using the injected logger to log a message.
logger.Log("Something happened.");
}
}
// Configuring and using DI container (e.g., Microsoft's built-in container).
public static void Main(string[] args)
{
var serviceProvider = new ServiceCollection()
.AddScoped<ILogger, ConsoleLogger>()
.AddScoped<UserService>()
.BuildServiceProvider();
var userService = serviceProvider.GetService<UserService>();
userService.DoSomething();
}
- We define an
ILogger
interface representing a dependency (logging). - We create a concrete implementation of the dependency (
ConsoleLogger
). - The
UserService
class depends onILogger
through constructor injection, meaning it receives anILogger
instance via its constructor. - We configure a DI container (in this case, Microsoft’s built-in container) to map the dependencies.
- We resolve the
UserService
from the container, which automatically injects theConsoleLogger
into its constructor when creating the instance.
What Dependency Injection Is Not
To fully grasp the concept of Dependency Injection, it’s essential to understand what it’s not. Consider the following non-DI example:
// A non-DI example where the class creates its dependencies directly.
public class UserService
{
private readonly ILogger logger = new ConsoleLogger();
public void DoSomething()
{
// Using the directly created logger.
logger.Log("Something happened.");
}
}
In this non-DI example, the UserService
class creates its ILogger
dependency directly within the class. This tightly couples the class to a specific logger implementation (ConsoleLogger
), making it less flexible and harder to test. It’s essential to highlight this contrast in your interview response to demonstrate your understanding of Dependency Injection principles.
Conclusion
Dependency Injection might have a fancy name, but at its core, it’s a simple and valuable concept in software development. It’s all about “giving an object its instance variables” and promoting loose coupling and testability.