Hi Steve, Dependency injection is a pattern most of us follow without realizing it. It deals with the principal of breaking dependencies between a client and a concrete object, but it evens goes further than just that.
>>[stay with me here, you might already know this but it's a good warmup for members new to oo principals]<<
For example, in the original post above, zoo faced a rather common architectural problem. He had 2 different ways of persisting an image(object). So he followed "Factor out what varies" and ended up with 2 concrete classes that does persisting in their own unique ways. Now each of the 2 classes is very cohesive, has a single responsibility and only one reason to change in the future.
Now, with those 2 classes at hand, he has to figure out how to correctly utilize them. I am pretty sure a client* will need to request the services of either one of those implementations at runtime, but should the client explicitly instantiate an instance of either? No. That will just defeate the whole purpose of pluggability, it leads to hardcoding and client code changes as new implementations come along. Robert C. Martin explained the Dependency Inversion Principal as:
"High level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details, details should depend on abstractions"
In other words, create an abstraction that represents the family of concrete low-level implementaions. Let each low-level module inherit or implement an interface of the abstraction. That will solve the latter "details should depend on abstractions".
So you end up with:
[IImageWriter]
^
|
|
[DbWriter] [FileWriter]
With that said, the client can now use the IImageWriter without having to worry about the low-level details of how images are being written, which solves the former "High level modules should not depend on low-level modules"
so now you end up with a diagram where the client is refering to an abstraction/Interface and the low-level details are referring/implementing that abstraction:
[aClient]
|
|
v
[IImageWriter]
^
|
|
[DbWriter] [FileWriter]
That explains the part where he says, "Both should depend on abstractions". IImageWriter has a very important purpose here, it forces the client to think at a higher level and not worry about how detailed things are done, it also encapsulates and hides all of the various implementations of persisting images and prevents the client from changing as new low-level modules are written. So it makes the design open for extension but closes the client from changing.
Now thats all fine and dandy but how exactly does the client use a specific implementation? I said earlier that the client should only rely on the high level abstraction and not the low level modules, but the client still needs a way to get a reference to an instance of a specific low level module. This is where you inject those dependencies.
Normally the client code might look like:
class ImageSaver {
IImageWriter anImageWriter = new FileImageWriter();
anImageWriter.Persist(anImage);
}
or
class ImageSaver {
IImageWriter anImageWriter = new DbImageWriter();
anImageWriter.Persist(anImage);
}
This solves just half of the problem, now the hardcoding of those concrete instantiations must be pulled out so that later when joe blow writes yet another low level module, it can be plugged in and work without any changes to the client.
Dependency Injection comes in 3 forms:
type 1 IoC (interface injection)
type 2 IoC (setter injection)
type 3 IoC (constructor injection)
And Martin Fowler does a great job at explaining this in his article:
There are also some mixing variations like global registries and reflection that solve these issues. To be honest, I use Reflection, Activator.CreateInstance("FileImageWriter") in my apps to inject dependencies and remove anything hardcoded.
In my next project I plan to make use of reflection with Registries to solve this issue.