If you find yourself doing this in your code then it’s something you need to learn!
I like to look at electronics and model them into classes in software.
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:
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.
I’ll use interfaces in C# to demonstrate the usage of injection and I’ll use Property injection.
1 | public interface ICutter |
There are 2 main types of injection:
Constructor
1 | public class Blender |
Method or Property
1 | public class Blender |
These types of injection support the Strategy Pattern.
Which you can read here:
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.
An important concept for DI are IoC (Inversion of Control) containers.
We eliminate the usage of the new operator.
A container is a unit that stores object types and how to “implement” them.
A container’s job is to create instances of classes.
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:
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…
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.
I’ll give 3 containers as examples:
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!