Inversion of control (IoC) and dependency injection (DI) can be quite confusing at first. I remember particularly struggling to figure out when exactly I should use DI\IOC, a question I’m sure most people have asked. Also, there is no clear definition for these concepts so a lot of reliance is on the examples of actual implementation. Anyways, my point is – Don’t be discouraged if you are confused at any point, just remember the more you familiarize yourself with these concepts the easier it to identify what they do and when you should use them.
I will be discussing the 3 most frequently asked questions about this topic.
- What is Dependency Injection and Inversion of Control?
- What problem does it solve?
- Why should you use it?
In order to answer these questions and ensure you get the most out of this post by providing meaningful examples, I urge you to first familiarize yourself with the following:
- Basic C# programming
- Interfaces and abstract classes
- Basic understanding of design patterns
What is Dependency Injection and Inversion of Control?
Generally, although these concepts each have specific definitions, the terms Dependency Injection and Inversion of Control are used interchangeably to describe the same design pattern. Hence some people say IoC Container and others say DI container but because they work hand in hand, by that I mean both terms indicate to the same thing and I like to think of them as two parts of a whole.
The term Inversion of Control (IoC) refers to a programming style where a framework or run-time, controls the program flow i.e. IoC allows you to change a flow or component in your application for example a data source, a resource file, service endpoints or even computation logic source – during run-time without changing code in your lower level classes (the classes which provide the actual implementation). Basically, when you are inverting control, you are moving control or decision making for lower level classes to higher level classes.
A basic example of where to use IoC – Let’s say you have been asked to build an application which allows a user to select an animal and based on the selected animal your application has to play the appropriate animal sound. For this example let’s assume your application only has two animal sounds, a dog bark and a cat mew [could even be more animals but we’ll stick with two for now]. From an application perspective we need to be able to change animal sounds whenever a different animal is selected. At this point if you are not familiar with design patterns, you will already start to think about using if statements in your main action method to check which animal was selected and play the appropriate sound. While that might be a quick and direct approach, it’s not always the best if we want to keep code open for extension, testable, maintainable and reusable. We definitely need to look at how to solve this problem using DI \ IoC.
Dependency injection complements IoC or makes it possible by enforcing loose coupling of components through the use of interfaces or abstract classes. Loose coupling basically means that objects particularly concrete classes are independent of one another. This is definitely important as it promotes better maintainability and re-usability of code.
Through dependency injection we can ensure that components are not tightly coupled i.e. components are not dependent on one another.
To explain tight coupling further just think of it as the inverse of loose coupling, it simply means that concrete classes are dependent on one another to fulfill their functions i.e. initializing concrete classes inside another concrete class is tight coupling. This is not best practice as object dependencies should be an interface and not other “concrete” objects.
What problem does it solve?
To better understand how to solve tight coupling, let’s first take a look at the problem it causes so we can appreciate the need to solve such a problem.
In my opinion tight coupling is always bad because it prevents the replacement or changes of components independently of the whole. It’s hard to argue in defense of coupling, and indeed hard to argue for it because it appears so self-evident. Now even with that said, I do think there are very special cases or very few cases where one may be able to explain why coupling is necessary or why it cannot be avoided. However, the rule is still the first option and still stands: – if you can decouple, then DECOUPLE.
Figure 1 above shows an example of tightly coupled classes – Class A initializes both Class B and C. One potential problem here will occur if I no longer need Class B and I decide to delete it. This will thus cause all sorts of errors in Class A which will mean that I will also have to go to Class A and remove all references of Class B. While I maybe describing this scenario in a small scale between only two classes, the bigger picture which you should see is that of a larger project where over 50 different classes are using or dependent on Class B which was suddenly deleted. The problem now is that you would have to go into every single class which depends on Class B and remove all of its references. Trust me that is not a pleasant exercise.
So how do we solve such a problem? As we start to think of a solution, let us recap on what we want to achieve. We want to be able to allow Class A to use functions available in Class B but we don’t want it to depend on Class B i.e. we don’t want Class A to initialize its own instance of Class B. This is where dependency injection enters. Before we go on to implement a solution lets first take look at some code to see the common mistake of tightly coupled objects.
In the above code illustration you will notice four classes, a Program class, Animal class, Cat and Dog classes. The code is based on the example mentioned earlier in this post. There are two animals and based on some trigger or condition we need to change from one animal to another so we can make the appropriate sound.
Although this code would work as expected, it’s not really a pleasure to maintain and extending it by adding another type of animal (e.g. horse) would require a lot more effort than necessary. What you always have to keep in mind about implementing any solution is how feasible is the solution on a large scale project.
For instance, if in this example we added more than just one animal behavior, let’s say we had ten or twenty different behaviors, imagine the effort it would take to add another three animals to this code e.g. a horse, a lion and a bird with their behaviors. You would have to create the classes for each animal, then initialize those class inside the Animal class and ensure that the if statement condition includes a check for the new animals and then call all the animal behaviors when the condition is true. Surely that alone should discourage anyone from tight coupling objects.
If you look at the Animal class you will notice that it depends on the both Cat and Dog classes. This is tight coupling as both classes are initialized inside the Animal class. The Animal class is also in control of where the specific animal is initiated and makes the decision of which Animal should make a sound. This should not be the case and we definitely need to invert that control to a higher class level, i.e. an indicator inserted from a higher level class should tell the Animal class which specific animal should make a sound, the animal class should not make the decision.
Compare the reworked code below with the code in the first implementation and note the difference.
The above code illustration now introduces a new component to the code example. The introduction of an Interface. Click on this link to get some great tutorials on interfaces. Both Cat and Dog classes inherit from the interface and I have created a constructor in the Animal class where I am injecting an interface.
Also, notice that the Animal class depends on an interface rather than a concrete class and because both the Cat and Dog classes inherit from the interface this makes them similar types, thus the injected object can be either a Cat or a Dog depending on the object passed in when the Animal class is called (In this example the Dog class is passed in from the Program class). The Animal class no longer has control of initializing the Cat and Dog classes and no longer depends on these concrete classes to call the functions within them (e.g. MakeSound). You should always try to invert control to the highest possible class level, in the above code we also achieve loose coupling by injecting an interface to break the dependency on concrete classes.
You will notice that the Program still initializes a new animal class and also passes in the animal object to be initialized. This solution still has some code dependency to lower level implementation classes. However, because control has been inverted to the highest class level possible, I can no longer invert this code. Now there are various way of achieving loose coupling at this level but for this example I will use the Factory Method pattern which will create a single point of reference for creating objects.
Don’t stress too much if you are thinking that things are now getting more confusing. The Factory Method pattern is really one of the most simply patterns to understand and implement.
The Factory Method pattern says: “Define an interface for creating an object, but let subclasses decide which class to instantiate”. That is the official principle for the Factory Method, but if you did not understand that definition at first, then that means we have something in common. So let me try and explain it in English.
The Factory Method basically assigns the responsibility of initializing or creating objects to a single class (more specifically to a single method in a class). To implement the Factory Method, I will add another class to the solution and the current Program class (highest class level) will initialize this newly created class which will be responsible for initializing all concrete classes we use for implementation. Using a single method to create objects is known as a single point of reference and ensures that we do not waste time searching through various places in our solution when a change occurs to our classes. Important to note – because we want to keep objects loosely coupled, the Program class needs to be depended on an interface or an abstract class, therefore the newly created class must inherit from an interface or abstract class.
Instead of using an interface this time, I will use an abstract class to implement the Factory Method. If you are not familiar with abstract classes, you can use this link to learn more. The concrete class which will create the objects must inherit from the abstract class and provide the implementation for the abstract method. The abstract method in our example is responsible and called when creating the actual class objects.
The above code illustration demonstrates how to add the Factory Method pattern to our initial code example using an abstract class. An abstract class must have an abstract method (line 151) and any class inheriting from the abstract class must provide the implementation for the abstract method. This pattern can be also implemented using an interface instead of an abstract class.
You should notice now that the Program class initializes the abstract class to a concrete class. The program class must know about the abstract and concrete classes, but because these classes form part of the pattern and are not implementation classes, they are not likely to change and dependence is on the abstract class instead of the concrete class. So, even if we are to add another 50 different animal type classes, the Program class does not need to know about them and will remain the same because the single point of reference is the concrete factory which creates the objects. The animal class which was present in Figure 2 and Figure 3 is now no longer needed and has been removed.
Based on the input (Line 113) which is read in, the factory class will create and return the appropriate object before calling the MakeSound function of that object.
If you do not want to return a new instance of an object, take a look the example in line 163. Here I am calling an existing instance of the Dog class, but if an instance does not exist then a new instance is returned (Line 134).
Why should you use it?
There are various reasons why you should use DI/IoC. It may seem like a lot of work initially to set up the pattern but the rewards far outweigh the effort. So, to answer the question why you should use these techniques let me rather just highlight a few advantages:
- Your code gets decoupled so you can easily exchange implementations of an interface with alternative implementations
- Greater re-usability and maintainability
- It is a strong motivator for coding against interfaces instead of implementations
- It’s very easy to write unit tests for your code because it depends on nothing else than the objects it accepts in its constructor/setters and you can easily initialize them with the right objects in isolation.
- Code is open for extension but closed for modification
I hope you found this post useful, please leave a comment if you have any questions. You can also check out a few well know DI\IoC containers listed below. I would advise that you first write your own code for DI\IoC before using these already built containers as that will give you a better understanding of the concepts of DI\IoC and you will be able to better understand these container, what they do and how they are put together.