Why do we need Dependency Injection?
Because ‘Dependency Inversion’ is the D in SOLID and dependency injection is the method we use to achieve this. End of.
Kidding.
It’s because Uncle Bob told us to.
Also kidding.
It’s to save you time and effort when maintaining or building new features into your application. It also makes your unit tests way more efficient and easier to write, but that might be a topic for another blog.
The General Idea
Imagine you have an online ordering system. You have an ‘Order’ class. Every order needs to send a confirmation email. You could add an ‘Order.SendConfirmationEmail()’ method that contains all the code to send the email. This would mean that your ‘Order’ class is dependent on a very specific implementation of sending emails. You’d also have an ‘Order’ class filled with email-specific properties. It would look something like this (although I’ve stripped it down for brevity).
It’s quite likely in an application that you’ll need to send emails from multiple places in the code, and if the method of sending emails changes (maybe you’re migrating to AWS and the in-built Microsoft smtp library isn’t going to cut it anymore), you’re going to have to change it everywhere. Can you really be bothered?
Instead, create an ‘EmailService’ class. In this class, have your email properties (to, from, cc, subject, message, etc.) and an ‘Email.SendEmail()’ method. The 'Order' class's 'SendConfirmationEmail()' method does whatever it needs to do and then calls the 'Email.SendEmail()' method, which configures and sends the email.
Every ‘Order’ object has an ‘EmailService’ object given to it in some way, and now your ‘Order’ class is dependent on some abstract idea of sending an email, rather than a hard coded implementation. When you need to change how the code sends an email, you change it in one place, and ‘Order’ doesn’t give a ham sandwich about how it’s done. You’ve just saved yourself a lot of time and effort.
The Code
When I was first learning this, I found a lot of the material completely baffling. I didn’t have the best grasp of interfaces at that point and I got bogged down in abstractions and interfaces and convoluted code examples. Don’t get me wrong, you need to know those things if you’re aiming to properly understand and use dependency inversion, but there is a lot to be gained from understanding the basics of dependency injection (without interfaces) and then building on them once you look deeper into the theory of dependency inversion.
Just for clarity, this is the code we're refactoring. (Before anyone points it out, no that code probably won't work... I didn't want to take up the whole page writing an actual function to send an email).
There are three main methods of dependency injection. I’ll order them by the frequency with which I use them.
1 - Constructor Injection
When you create an object, pass the object it’s dependent on into the constructor and set it as a private property. In this example, we have an 'Email' class as a constructor parameter, and a private property '_emailService'.
When we create a new ‘Order’ object, we pass an ‘Email’ object into the constructor, which then assigns it as a private property.
2 - Method Injection
For this, create your 'Order' class with a 'Order.SendConfirmationEmail()' method. Require an 'Email' object to be passed as an argument to the method.
To use this, instantiate your 'Order' object, then call the 'Order.SendConfirmationEmail()' method and pass it an 'Email' object.
3 - Property Injection
When creating your class, add a public property of 'Order.emailService'. In the 'Order.SendConfirmationEmail()' method, call 'emailService.SendEmail()'.
To use this, instantiate the 'Order' class and then assign an 'Email' object to the 'Order.emailService' property.
Disclaimer: I never use this. That property usually doesn’t need to be public for any reason other than the external assignation of the object, so it’s exposing properties for no real reason. Properties and methods should be private by default unless something external absolutely needs them.
If you're going to implement dependency inversion properly, I would recommend looking into the full theory and getting comfortable with using interfaces, but this is the nuts of bolts of how dependency injection can be done. The rule I live by is to never instantiate an object within a class. If you catch yourself doing that, delete it and pass it in instead. Comment your thoughts!