So I have my application consisting of a number of modules in a module hierarchy. Furthermore let's also assume each module is a class and we have a tree of classes where the classes at the top are using the classes below, to make it more simple.
A class A1
at the very bottom may depend on some input parameters. class B1
is above class A1
and is creating and using instances of class A1
. Therefore it has to pass the dependencies needed by the instances of class A1
into them. If it can't create these dependencies from some operations, class B1
now has it's on dependencies but additionally the dependencies of class A1
.
The higher we go the more these dependencies will add up so that the toplevel class
will need to know all the dependencies unless they can be created at a lower level.
This means if class A1
my program is dependent on the current temperature
, I have to pass this to the toplevel class
which then passes it to the next class
and so on until it arrives at the very bottom in class A1
. If I do that, I make the state explicit but it also means that I have methods or classes that take many parameters.
Variables
What if the temperature
is may change while the application is running – is there a way to avoid passing it all the way down the class tree without giving away explicit state? How would you guys handle this?
Constants
What if the temperature
is a constant that will never change while the program is running? Does this give us more options to avoid passing it always as an argument? I could see someone using a global configuration (singleton) but it will make it harder to test right?
I could also pass not the temperature
itself but a configuration object. This would mean class B1
does not receive for example a temperature
and a airpressure
parameter but gets a configuration object passes it to class B1
and class B2
where class B1
only needs the airpressure
and class B2
only needs the temperature
. Is that a good approach? What are the pros/cons?
Best Answer
This is exactly the reason why DI containers exist. I will give you an example for Castle Windsor, but it should apply to other containers, with some modifications.
Class definitions:
Container configuration:
This is a very basic example of what you can do with a container. The types used by the application must be made known to the container. When asked to create an instance the container will search the constructor for dependencies and will create them from the types it knows about. This is called constructor injection because the constructor is used to specify the dependencies for a class.
Type resolving:
Dependency injection introduces a concept called composition root, which is the place where all the components of your application are put together (the configuration section above is placed in here). The container should (I'm using should because there are some exceptions to this rule) be used explicitly only in the composition root.
None of the classes of your application that are not related to infrastructure are allowed to have the container as a dependency. If you need to break this rule then it's a clear indication of a design flaw.
Let's assume that we have a desktop application, with a main form that uses our
B1
class.The composition root for this application is the entry point (for .net windows applications the entry point is
Program.cs
). I have madeMainForm
known to the container in the configuration code above. All I have to do now is obtain an instance ofMainForm
and show it to the user.At this point, the container searches its list of known types for
MainForm
, looks in the constructor for any dependencies and it tries to resolve them. It will move through the hierarchy down to the last type. By the time it finishes it will have created instances for all the types in the hierarchy (if the types are found in the container).If the temperature is variable then I would use a class with a simple method called
GetTemperature
and inject that class wherever I need it. The method presumably performs some logic to retrieve the temperature. If the system must respond to temperature changes then the classes that depend on it can be observers of the class that emits these changes.If a class depends on multiple configuration constants then it's perfectly fine to group them in a single class and pass that class as dependency where it's needed. Castle has an adapter that helps in this particular case.
I recommend reading Mark Seemann's book (Amazon link) on dependency injection, it is very well written and is, in my opinion, the best book on the subject.