Dependency Injection (DI) is a design pattern that implements Inversion of Control (IoC). Fine but what does that even mean? First time I read that, it didn’t give me any answers. In short a design pattern is a way of coding. Design patterns are repeatable templates for writing more maintainable, more testable, and less bug prone applications. Think of it like a set of instructions for how to organize your classes, methods and implementations.
So to answer the quesiton: “Why do we need interfaces?” I provide the 3 most popular reasons why:
1. Unit Testing
The most important reason of all is Unit Testing. Mocking a concrete implementation of a class is considerably more difficult than an interface/abstraction. Most frameworks MSTest/NUnit integrate painlessly with mocking tools such as Moq, allowing for easy to write/read/maintain unit tests (link). Mocking interfaces ensures the same result is returned every time the test is run. Furthermore, someone gave an excelent example of why interfaces are 100x better than classes: Say you want to test a file logger. If an interface is used, you can simulate writing to a file, then reading it, finally asserting the expected with the actual result. But if you test the actual FileLogger, you will populate the file system with unnecessary files, which will become a nightmare after just a few weeks of deployments.
2. Defining Dependencies
Say you work on a project management web app. You are assigned to implement a module that sends notifications each time a task has been finished. But you don’t know yet what service you will be using to send these emails. Easy, you write the functionality for the module and when it comes to email notifications here’s how it goes:
⦁ Define an interface IEmailService
⦁ Define the method/s you intend to consume: i.e: Send()
⦁ Now declare IEmailService at the top of your API/Controller/Caller Service, initialise it in the constructor and use it.
This way, the new functionality can be tested as usual, the email service is just another dependency to mock inside your unit tests (more about this at the end).
Later, as soon as you/the management decides what service you’ll use, replace the implementation with SMTP/SendGrid/Mailgun etc.
3. Code Maintainability
Making use of interfaces makes the code more maintainable. By defining an interface, the code is no longer dependent on a concrete implementation of a service/class. It is dependent on an abstraction of that class. This means the same service (say Logger) can be used throughout the entire project, without having to new up a new instance every time the consumer class is instantiated.
Having said that this is not a one size fits all. I always here/read people saying “It’s best practice, you should just do it!”. That’s actually not the point. What’s more, some patterns, DI included, might not actually make it easier to maintain in some cases. Sometimes the effort increases (more code/boilerplate to write), there takes more to write. So take it with a pinch of salt.
Finally, those are the 3 reasons. The most important one being Unit Testing. Make your life easier by working with abstractions rather then concrete implementations
If you want more content like this, sign up to my weekly emails with posts just like this. No spam, unsubscribe anytime.