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?
-
A class generally shouldn't inflict its dependencies on classes higher up in a hierarchy. In other words, a class high in a hierarchy should only need to know about common dependencies of all subclasses.Mike Partridge– Mike Partridge2014年05月22日 15:36:36 +00:00Commented May 22, 2014 at 15:36
1 Answer 1
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:
public class A1
{
public A1(decimal temperature)
{
_temperature = temperature;
}
private readonly decimal _temperature;
// what follows is specific to the possible methods that make use of temperature
}
public class B1
{
public B1(A1 a1)
{
_a1 = a1;
}
private readonly A1 _a1;
// possible methods that make use of a1
}
Container configuration:
// this assumes a C# environment
var temperature = Convert.ToDecimal(ConfigurationManager.AppSettings["temperature"]);
IWindsorContainer container = new WindsorContainer();
container.Register(Component.For<A1>().DependsOn(Property.ForKey<decimal>(temperature)));
container.Register(Component.For<B1>());
container.Register(Component.For<MainForm>());
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.
public class MainForm : Form
{
public MainForm(B1 b1)
{
}
...
// do something with b1
}
The composition root for this application is the entry point (for .net windows applications the entry point is Program.cs
). I have made MainForm
known to the container in the configuration code above. All I have to do now is obtain an instance of MainForm
and show it to the user.
public static class Program
{
// container configuration goes here
// type resolving follows
var mainForm = container.Resolve<MainForm>();
// show main form the 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.
-
So that means when
C1
wants to create a newB1
it will not donew B1(new A1(temperature), dynamicB1Param)
but insteadcontainer.giveMe<B1>(dynamicB1Param)
? I'm not sure if I got it right. Because if I did, that would mean, I have to pass the container (instead of all the other parameters) into the constructor right? (you did not do in your example so I am a bit confused) But that would mean, I would pass an object into e.g.B1
which knows how to create aC1
even thoughB1
will never need and should never use aC1
right? That somehow feels not elegant to me.valenterry– valenterry2014年05月22日 18:16:09 +00:00Commented May 22, 2014 at 18:16 -
@valenterry i've updated the answer with an example for type resolving.devnull– devnull2014年05月22日 19:15:26 +00:00Commented May 22, 2014 at 19:15
Explore related questions
See similar questions with these tags.