4

Is it a pattern or antipattern to inject a dependency that is a tree of dependencies?

For example:

public class ClassExample
{
    private IContainer _container;

    public ClassExample(IContainer container)
    {
        _container = container;
    }
}

public interface IContainer
{
    IDependencyA DependencyA { get; set; }
    IDependencyB DependencyB { get; set; }
    IDependencyC DependencyC { get; set; }
}

public interface IDependencyA
{
}

public interface IDependencyB
{
}

public interface IDependencyC
{
}

The above example could be made even deeper (a tree) - for example IDependencyA could contain even more dependencies IDependencyAA, IDependencyAB, etc.

Someone might use above method to minimize code duplication when having multiple places where a same set of services need to be injected. Code will end up looking like: container.DependencyA.DependencyAA.Method(...) in many cases which makes it more verbose and less DRY.

Is using above method a good or bad idea (pattern/antipattern)? Or when is it a good idea, and when is it a bad one?

2 Answers2

9

container.DependencyA.DependencyAA.Method(...)

That looks like it might be a Law of Demeter violation. The problem has nothing to do with dependencies.

The issue is reaching past objects and violating their abstraction. This is an issue because if even one client writes code like: container.DependencyA.DependencyAA.Method() then that whole chain is set in stone. You have no freedom to build it any other way. If the client had referred to it as depAA.Method() then we'd have some flexibility.

Now this isn't to say that you never want to see a chains of dots. After all the Law of Demeter is not a dot counting exercise. Long dot chains are fine when you've been promised that the path will always hold true. For example Java8 streams have you dotting together wonderfully long chains. That's OK because those chains are expected. You have been promised that they won't change.

The problem is when client authors go delving into the code base and stitch together whatever paths happen to work. Now this carefully decoupled code base is bound together in a ball of mud because someone kept reaching for what they needed rather than simply asking for it to be handed to them.

The idea is that it's better for each object to have a few friends that it knows how to talk to. Those friends have friends they know how to talk to. If you talk to your friends friends soon you have to know how to talk to everyone. That's bad if we ever want to be able to implement a change because everything ends up being impacted by the change. Value not knowing to much.

Use facades, abstraction, and dependency injection to keep clients from knowing how your object graph is wired together and you retain the freedom to wire it together as you see fit.

If you must allow for chains then be sure to decouple the chain from the implementation underneath. These are called internal Domain Specific Languages (iDSL). They are very powerful but take a lot of work to set up. The allowed chains form the mini language. Be sure you can swap out the implementation behind the chain.

candied_orange
  • 102,279
  • 24
  • 197
  • 315
  • It looks like the OP might be using C# as a language, in which case the code could be accessing properties. A property in C# is just fancy-talk for a getter method returning a backing field. That wouldn't be a violation of the LoD, as it applies to exposing the internal state of an object. All though to be honest, properties in C# are used so frequently as a stand-in for fields to return internal state it often feels like a violation of LoD... – Greg Burghardt Nov 29 '18 at 23:18
  • @GregBurghardt I see nothing to make me think that isn't exactly what is happening here. Though, with these terrible names for all I know these are all collections. – candied_orange Nov 30 '18 at 00:06
1

It is okay provided that:

  1. The passed set of dependencies is a simple container,
  2. and the collection of dependencies genuinely belong together,
  3. and the members of the collection are (mostly) real dependencies of the class the collection is being injected into.

Don't get led astray by the Principle of Least Knowledge (more cryptically known as the Law of Demeter): it is totally appropriate for a class to know about their dependencies, and what you're talking about is a way to provide those dependencies to the class not a separate functional structure that should be being shy about its internals. This is what the first of the guidelines above is about: your set of dependencies should be simply a set of dependencies, once the class you are injected has its own independent state and behaviour, the passed set of dependencies become part of the implementation of that class and the clarity of separation is lost.

However, packaging together for convenience can quickly become a bloated monolith that allows classes to get all up in each others' internals by passing around too many dependencies that simply don't have any business receiving access to many of the classes included in the set, and a means to avoid having to put any thought into what dependencies are injected. After all, if you're going to do that you may as well make your dependency set a singleton and be done with it; it's effectively become a clumsier way of doing the same thing. This is what the second and third of the rules above are about. The dependencies packaged together should have a natural connection, not simply be a bunch of things it's easy to pass around, which ensures the passed set is meaningful in itself. You should also ensure that most of the objects receiving the set should be using most of the items in the set most of the time, otherwise you're not injecting dependencies, you're just providing access to general program state.

Jack Aidley
  • 2,954
  • 1
  • 15
  • 18