If you find yourself doing this in your code then it’s something you need to learn!
What is Dependency Injection?
I like to look at electronics and model them into classes in software.
What is a dependency?
A dependency is something a unit of work relies on.
For example a Blender is dependent on its knife - Without a knife it wouldn’t function.
Some example also include:
- A data layer that’s dependent on a DB Functionality.
- A presentation layer that’s dependent on a Window.
What is an injection?
Putting a unit of work or data into other unit of work.
A blender is actually an excellent example:
It gives us a way to put a kind of blade into a socket - by the type of the knife we change the functionality of a blender.
Code example
I’ll use interfaces in C# to demonstrate the usage of injection and I’ll use Property injection.
1 | public interface ICutter |
Types of Code Injections
There are 2 main types of injection:
Constructor
1
2
3
4
5
6
7
8
9public class Blender
{
private ICutter mCutter;
public Blender(ICutter cutter)
{
mCutter = cutter;
}
}Method or Property
1
2
3
4
5
6
7
8
9
10
11
12public class Blender
{
private ICutter mCutter;
public void SetCutter(ICutter cutter)
{
mCutter = cutter;
}
// OR
public ICutter CurrentCutter { get; set; }
}
These types of injection support the Strategy Pattern.
Which you can read here:
Decoupling
The main advantage of DI is the decoupling of unit of works into independent units.
So in our example a Blender is decoupled from the knife - the knife is not built in.
Meaning it’s easier to replace, maintain clean and to fix the knife.
Just like code!
Modular units are easier to maintain and fix - if the cutting isn’t right we know which unit does the bad behavior.
As I’ve written in the Strategy Pattern Here it’s also easier to test smaller units.
Dependency Containers
An important concept for DI are IoC (Inversion of Control) containers.
We eliminate the usage of the new operator.
What is a DI container
A container is a unit that stores object types and how to “implement” them.
A container’s job is to create instances of classes.
Usual interface of a container
I’ve implemented already a good container with registration and lifecycle, here’s how an interface looks like:
1 | public enum InstanceLifecycle |
The lifecycle of a dependency is:
- Registration - Marking of how an instance T should be created - this includes its own dependencies.
- Instantiation - Creation of an instance and the rules of its life.
- Destruction - How an instance is destructed.
ImageSaver consists of ICompressor and IStore.
An instance of IStore (Implementation) is dependent on IFileIO.
Registration usually works on a main level of abstraction:
1 | Container.Register<IStore, FileStore>(); |
Then a resolve is made:
1 | // Store doesn't know the underlying type! |
The conatiner can implement it in many ways.
store instance may be a singelton, pooled, new instance, etc…
Named instances
What if we have 2 implementations?
1 | IDIContainer RegisterNamed<TInstance,TImp>(string name); |
Named registration can resolve this conflict.
We can make our code fancier by introducing Attributes and Meta-Data code.
Containers in the wild
I’ll give 3 containers as examples:
Dependency Containers are awesome
In the end using DI and IoC(Inversion of Control) containers give us full power of instantiation of classes.
The only new we used is on the container, everything else is done by the container.
Some containers are smart and can resolve unregistered instances if they know how to construct them:
1 | public void Main() |
This gives us access to maintaining lifecycle and modularity very easily, I like it.
Thanks for reading!