This post was authored by guest blogger André Obelink, a Visual Basic MVP, and published by the VBTeam on his behalf.
In this first blog post of a series of two, I explain what dependency injection (DI) is and why you might want to use this design principle in your software. The target audience of this post is the junior / medium experienced software developer, with no knowledge of dependency injection or related techniques. In the second post, I’ll describe the use of Inversion of Control Containers (IoC containers), to use dependency injection in a much more flexible way.
What is dependency injection?
Dependency injection isn’t new. It’s been around since the early 90s. Because it’s a design principle, it isn’t directly related to a specific programming language. For example, you can use this principle in Java, C#, or Visual Basic .NET. It’s one of the principles of SOLID. SOLID is a mnemonic acronym for the five basic principles of object-oriented programming and design. In my daily life as a software developer, I try to commit myself to apply the principles of SOLID. They were, years ago, defined by Robert C. Martin (also known as ‘Uncle Bob’). He’s the author of many books, including ‘Clean Code’. A highly recommended book!
Let’s start from the beginning by defining the term ‘dependency injection’. However, this is easier said than done. Wikipedia says: “In software engineering, dependency injection is a technique whereby one object supplies the dependencies of another object. A dependency is an object that can be used (a service). An injection is the passing of a dependency to a dependent object (a client) that would use it. The service is made part of the client’s state. Passing the service to the client, rather than allowing a client to build or find the service, is the fundamental requirement of the pattern.”
If you’re new to this subject, you may read this definition more than once and still have no clue what exactly is meant. I think an example from real life might help.
When you were a child, and you asked your mother to give you something to drink, you were happy when she handed you a cup of milk, a glass of orange juice, or a bottle of apple juice, for example. Your mother was in charge. You asked for a drink, but you didn’t know what you’d eventually get. The fact that you knew that you’d get some drink, without knowing exactly which drink it would be, describes more-or-less the core concept of dependency injection. You don’t know the exact type you’ll get, but you know about, for example, the methods on the interface.
Let’s look at another example from daily life, more related to computer software. Perhaps you’re familiar with Adobe Photoshop and their mechanism of plug-ins. You want to apply a filter to a photo. You can install a filter or plug-in, published by a third-party vendor. Adobe Photoshop doesn’t know anything about that filter. However, when this plug-in is installed, it can execute the filter logic, and apply that specific implementation of the filter to the active photo. Again, there’s a concept of not knowing about the filter that is installed, and the ability to apply that ‘unknown’ filter.
In my daily life as a developer, I use dependency injection a lot. Imagine, for example, the logging components we’re using in our software, like Log4net. Based on configuration, it logs to different – or a combination of – output destinations. In our code, we call something general like _logger.Log(“Hello!”). But when writing that code, we don’t know where this text will end up. Is it in a text file or is logged to the Windows Event log? Or both? Or will an email be sent containing the message?
It’s important to understand that dependency injection involves four roles:
- The service objects to be used.
- The client object that is depending on the services it uses.
- The interfaces that define how the client may use the services.
- The injector, which is responsible for constructing the services and injecting them into the client.
The service objects are the actual implementations of the logic you want to execute. In the example of the child asking for a drink, there are three ‘DrinkServices’: the MilkDrinkService, the OrangeJuiceDrinkService, and the AppleJuiceDrinkService.
The Child is the client object. It calls the Drink() method in one of the services. You can ask yourself the question: “How does the Child know that it can call something like a Drink() method?” As you perhaps know, the Child is not aware – and not allowed to be aware – of the MilkDrinkService or one of the other services.
The Child knows about the Drink() method, because it calls a method on an interface, in our example the IDrinkService. An interface defines the public members, but doesn’t implement them. See it as a contract or a description of the signature of the class. A service object, which implements an interface, contains the actual logic.
You can compare the mother of the child with the injector. She’s responsible for what kind of drink her child gets. The injector injects the actual logic.
Do some coding
I think that some code will clarify this abstract theory much better. It’s a best practice to start with defining the interface. In this case, the IDrinkService.
As you can see, it only defines which public methods are available. Because it always defines the public members, you must omit the keyword Public. And as I said earlier, it doesn’t contain any implementation.
The service objects implement the logic of the interface. The MilkDrinkService() implements IDrinkService. In practice, this means that we will write the specific implementation of the Drink() method in this class.
After you type ‘Implements IDrinkService’ and hits Enter, Visual Studio helps you by creating the method for you. Replace the ‘Throw New NotImplementedException()’ with the specific logic you want to execute in that service object. We have now implemented one of the service objects. The other two are quite similar. They differ only in the specific logic. Let’s add the second service object.
And the last service object isn’t hard to guess:
We now have coded all service objects. The next step is to define the client object. As you can remember, we must inject the service object into the consuming client object. There are a couple of ways to inject dependencies, but I’m choosing to use ‘constructor injection’. Other techniques are, for example, ‘setter injection’ or ‘interface injection’. The first example is self-explanatory: the client object exposes a setter method that the injector uses to inject the dependency. The second example is harder to understand. Interface injection concludes that the dependency provides an injector method that will inject the dependency into any client passed to it. Clients must implement an interface that exposes a setter method that accepts the dependency. For now, forget these other two injection techniques and let’s focus on constructor injection. In most cases, I use this type of injection because it always brings my client object in a valid state. To understand what I mean with this last statement, we should take a close look at the code.
As you can notice, I added a constructor that forces to apply an argument of the type IDrinkService. This means that we can only create an instance of this object, when we provide an object that implements this interface. So, an instance of my client object has always the mandatory _drinkService set. When other developers use one of my DrinkServices, they’re ‘unable’ to instantiate them in a wrong manner because Visual Studio is already telling them at design time that some mandatory information is missing (In comparison with setter injection, where this missing piece of code is only noticed when you’re already running the code).
Note that the client object has no references to – or knowledge of – our service objects. It’s only aware of the methods of the interfaces that it implements.
The fourth step is creating the injector. The injector is responsible for defining and injecting the dependency, in our example on of our DrinkServices.
You can imagine that the Mother class returns, based on some business logic or external configuration, one of the other services. The last part of our code example is using our classes.
When executing this code, this results in the following output:
You can play with this example by changing the implementation of the Mother.GetDrinkService() method. Our client object isn’t aware of the actual services. And if you decide tomorrow to create a BeerDrinkService() method that implements the IDrinkService interface, all depending code will still run fine.
Why using dependency injection?
The concept of dependency injection introduces some abstraction to your business logic. Some developers experience this as adding extra complexity to the code base. Other developers, including myself, think that it just removes complexity. For me, the main reason to apply these design principles is that it makes my code more loosely coupled. Loosely coupling helps you make your code better maintainable, extensible, and testable. It helps your applications to get more configurable: you can even change logic without recompiling.
By applying dependency injection, you will find that your classes contain in most cases only one responsibility and that’s a good thing. Single responsibility is one of the other five principles of SOLID. Indeed, you need dependency injection for a couple of other techniques defined by SOLID. I’m convinced that applying these techniques helps you write better software.
When writing unit tests, you will love these techniques even more! Imagine you have business logic that calculates the total amount of an order. The order, including the order lines, is retrieved from the database by an OrderRepository that implements the IOrderRepository.GetOrderByNumber() method. When you want to write a unit test that calculates the total order amount, this code relies on data in the database. But this is not what you want, because when data in your database is changed, your unit test will fail. Also, when running this unit test as part of the automated build in, for example, the cloud service VSTS, it can’t even connect with your database on premise.
So, to overcome this issue, when you want to unit test this piece code, the solution is to inject another service object that implements IOrderRepository too. For example, FakeOrderRepository. This fake or mock implementation will create an order and related lines in code. This code won’t normally change and can be executed independent from a database. Your unit tests will be much more predictable.
In this post, I have explained the concept of Dependency Injection. By introducing interfaces and implement them with a concrete and specific logic, your code will be more flexible, easier to understand, to extend and to maintain.
In the next post, I’ll show you the use of IoC containers, a technique that will help to assemble your application and other components by using dependency injection to a cohesive program.