10

Every time someone reaches me and asks me to define the Dependency Injection in a conceptual way and explain the real pros and cons of using DI in software design. I confess that I have some difficulties to explain the concepts of DI. Every time I need to tell them the history about single responsibility principle, composition over inheritance, etc.

Anyone can help me explaining the best way to describe DI for developers?

  • 2
    The challenge here is that there are so many conflicting definitions of DI. I take the "pure DI" stance: if I have a function that relies on its parameters to provide all state, data etc, then that function is using DI. At the other extreme, some will argue that without a DI framework, there is no dependency injection (though they are wrong of course ;)). So unless you can nail down a definition, you can't begin to explain what it is... – David Arno Feb 20 '19 at 16:17
  • So, as I understand, this is not only a problem of mine. – Tiago Sampaio Feb 20 '19 at 16:26
  • 1
    see How do I explain ${something} to ${someone}? – gnat Feb 20 '19 at 16:55
  • 1
    Related: stackoverflow.com/questions/1638919/… – Liath Feb 20 '19 at 17:20
  • It all comes down to this: dependency injection is one technique used to achieve dependency inversion; everything else is just extra stuff built on top of that. Note that in these two terms the word "dependency" has slightly different meanings. In dependency injection, it refers to the component that the code is dependent upon. In dependency inversion, it refers to the (directed) relationship itself - the one that we want to invert. The latter is the goal, so main pros and cons are the same; plus some extra concerns related to actual implementation, like object lifetime management. – Filip Milovanović Feb 20 '19 at 17:55
22

Dependency Injection is a horrible name (IMO) 1 for a rather straightforward concept. Here's an example:

  1. You have a method (or class with methods) that does X (e.g. retrieve data from database)
  2. As part of doing X, said method creates and manages an internal resource (e.g. a DbContext). This internal resource is what's called a dependency
  3. You remove the creating and managing of the resource (ie DbContext) from the method and make it the caller's responsibility to provide this resource (as a method parameter or upon instantiation of the class)
  4. You are now doing dependency injection.


[1]: I come from a lower-level background and it took me months to sit down and learn dependency injection because the name implies it'd be something much more complicated, like DLL Injection. The fact that Visual Studio (and we developers in general) refers to the .NET libraries (DLLs, or assemblies) that a project depends upon as dependencies does not help at all. There is even such a thing as the Dependency Walker (depends.exe).


[Edit] I figured some demo code would come handy for some, so here's one (in C#).

Without dependency injection:

public class Repository : IDisposable
{
    protected DbContext Context { get; }

    public Repository()
    {
        Context = new DbContext("name=MyEntities");
    }

    public void Dispose()
    {
        Context.Dispose();
    }
}

Your consumer would then do something like:

using ( var repository = new Repository() )
{
    // work
}

The same class implemented with dependency injection pattern would be like this:

public class RepositoryWithDI
{
    protected DbContext Context { get; }

    public RepositoryWithDI(DbContext context)
    {
        Context = context;
    }
}

It's now the caller's responsability to instantiate a DbContext and pass (errm, inject) it to your class:

using ( var context = new DbContext("name=MyEntities") )
{
    var repository = new RepositoryWithDI(context);

    // work
}
  • 3
    This should be added to Wikipedia. – Evorlor Feb 21 '19 at 13:11
  • 2
    It's now the caller's responsability to instantiate a DbContext - I think this would be responsibility of application's entry point to instantiate all required dependencies. So consumer only need introduce required type as a dependency in own contract. – Fabio Mar 8 '19 at 20:34
  • @Fabio It could be. (In that case the caller's responsibility would to provide the resource that was instantiated at application startup to the method/class being called.) In my example, it isn't, though, because that's not a requirement to explain the concept of dependency injection. – Marc.2377 Sep 22 '19 at 5:20
5

Abstract concepts are often better explained using a real world analogy. This is my go-to analogy:

You run a sandwich shop. You make amazing sandwiches, but you know little to nothing about bread itself. You only have bland white bread. You job wholly focuses on the toppings you use to turn the bread into a sandwich.

However, some of your customers would really prefer brown bread. Some would prefer wholegrain. You don't really care either way, you can make any amazing sandwich as long as it's a bread of similar size. You also really don't want to have to take on the added responsibility of procuring several types of bread and keeping stocks up. Even if you stock several types of bread, there's always going to be some customer with some exotic taste in bread that you could not reasonably foresee.

So you institute a new rule: customers bring their own bread. You no longer provide any bread yourself. This is a win-win situation: customers get to have the exact bread they want, and you no longer have to bother with procuring the bread you don't care about. After all, you're a sandwich maker, not a baker.

Oh, and to accomodate those customers who don't want to buy their own bread, you open up a second shop next door which sells your original bland white breads. Customers who didn't bring their own bread simply have to get the default one and then come to you to make a sandwich with it.

It's not perfect but it highlights the key feature: giving control to the consumer. The inherent win-win is that you no longer have to acquire your own dependencies, and your consumer is unhindered in their choice of dependency.

  • 1
    I like this but the OP is looking for an explanation for developers. An initial abstraction is good, but sooner or later, it would need a real life example. – Robbie Dee Feb 21 '19 at 12:48
  • 1
    @RobbieDee: When the purpose of the pattern is clear, its implementation tends to become clear as well. For example, Marc's answer is absolutely correct, but I feel like the explanation is getting bogged down by the complex nature of the example situation he uses. This boils down to "If you want to build a ship, don't drum up people to collect wood and don't assign them tasks and work, but rather teach them to long for the endless immensity of the sea.". Rather than explain what to do, I prefer explaining why to do it. – Flater Feb 21 '19 at 12:58
  • 2
    You're right of course, but I can't help but think I'd need a tangible example - such as not having a real file system or database to whet my appetite but maybe that is just my narrow developer view :) – Robbie Dee Feb 21 '19 at 13:09
1

Simple answer to that:

First of all a class should have a well-defined responsibility, and everything outside this scope should be kept outside that class. With this said, Dependency Injection is when you inject a functionality from other class B into a class A using help from a "third-party" to achieve this separation of concerns, helping class A to complete some operation that is outside of its scope.

.Net Core is a pretty good example that you can give because this framework uses a lot of dependency injection. Generally, the services that you want to inject are located at the startup.cs file.

Sure, the student should be aware of some concepts like polymorphism, interfaces and OOP Design principles.

0

There is a lot of fluff and bunkum around what is, in essence, a simple concept.

It is also very easy to get bogged down with "what framework I should use" when you can do it quite simply in code.

This is the definition I personally use:

Given behaviour X with a dependency of Y. Dependency injection involves the facility to supply any Y that satisfies the criteria for being an instance of Y, even if you don't have one.

Some examples might be where Y is a file system or database connection.

Frameworks such as moq allow doubles (pretend versions of Y) to be defined using an interface so it is possible to inject in an instance of Y, where Y is for example, a database connection.

It is easy to fall into the trap of believing that this is purely a unit testing concern but it is a very useful pattern for any bit of code where change is expected and arguably, is good practice anyway.

0

We provide a function's behavior at runtime through the method of inserting that behavior into the function through a parameter.

Strategy Pattern is an excellent example of dependency injection.

0

To do this right, we must first define dependencies and injection.

  • Dependency: any resource an operation needs.
  • Injection: passing that resource to the operation, typically as an argument to a method.

A rudimentary example would be a method that adds two values. Obviously, this methods needs the values to be added. If they are provided by passing them in as arguments, this would already be a case of dependency injection. The alternative would be to implement the operands as properties or global variables. That way no dependencies would be injected, the dependencies would be externally available upfront.

Suppose you use properties instead and you name them A and B. If you change the names to Op1 and Op2, you would break the Add method. Or your IDE would update all the names for you, the point is the method would need to be updated as well because it has dependencies to external resources.

This example is basic but you can imagine more complex examples where the method performs an operation on an object like an image or where it reads from a file stream. Do you want the method to reach for the image, requiring it to know where it is? No. Do you want the method to open the file itself, requiring it to know where to look for the file or even to know it will be reading from a file? No.

The point: to reduce a method's functionality to its core behavior and decouple the method from its environment. You get the first by doing the second, you may consider this the definition of dependency injection.

The benefits: since dependencies for the method's environment have been eliminated, changes to the method will not impact the environment and vice versa. => The application becomes easier to maintain (modify).

Not the answer you're looking for? Browse other questions tagged or ask your own question.