How to handle passing multiple dependencies in a module hierarchy

class-designclean codedependenciesdependency-injection

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:

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.

Related Topic