8

I've recently been reading through Mark Seemann's Dependency Injection in .NET and have been attempting to apply some of what I've learned in a new project I'm working on, and I'm stumped on a particular scenario trying to determine what dependencies I should and should not inject. I've read several other questions on SO and elsewhere that discuss similar scenarios but in a roundabout manner, and I think I've narrowed the underlying conceptual question to that which is in the title:

Can a stable dependency have a volatile dependency? Put another way: If a stable dependency has a volatile dependency, is the stable dependency really stable?

A quick example:

public class MyClass
{
    private IVolatileDependency1 _one;
    private StableDependency _stable;

    public MyClass(IVolatileDependency1 one, IVolatileDependency2 two)
    {
        _one = one;
        _stable = new StableDependency(two)
    }
}

public class StableDependency
{
    private IVolatileDependency2 _two;

    public StableDependency(IVolatileDependency2 two)
    {
        _two = two;
    }
}

In this situation, MyClass has a stable dependency which I've chosen not to inject but instead instantiate internally and hold via composition. However, that stable dependency has a volatile dependency, which I'm injecting into MyClass and passing through to StableDependency's constructor.

For whatever reason, this smells a little bit to me. It's fairly straightforward in such a simple contrived example, but I can see things getting out of hand quickly in a more complicated real-world situation.

Is StableDependency really stable, or should I be composing/resolving it prior to constructing MyClass and injecting it from there? I really don't foresee StableDependency changing or being replaced by another implementation, but on the other hand the practice of passing dependencies down through parent classes to their children (parent/child in the composition sense) seems to expose the details of the children up through the parent, which would be a violation of the Law of Demeter.

EDIT: Changed VolatileDependency1 and VolatileDependency2 to interfaces to clarify that they are intended to be satisfied by multiple possible implementations (LSP-compatible).

EDIT 2: Thinking on this some more, and reading KeithS's answer and default.kramer's comment below, make me realize that maybe I shouldn't have named StableDependency as I did, because I don't think it's a dependency in the first place. It's not something I ever expect to have multiple implementations for; it's merely an implementation detail that MyClass uses composition to contain a StableDependency, so MyClass's knowledge of how to construct a StableDependency isn't a problem I don't think. The big question is, StableDependency does have a dependency on IVolatileDependency2, so how do I inject that dependency? Do I reason that for all intents and purposes MyClass has a dependency on IVolatileDependency2, and the fact that it just passes that dependency down to StableDependency is an implementation detail unrelated to DI?

1
  • 4
    I would definitely inject the StableDependency. I don't want to know how to construct one, I just want one. Plus, I wouldn't want to repeat the construction logic in other classes. Commented Aug 14, 2012 at 17:16

2 Answers 2

10

If the StableDependency can accept any VolatileDependency2 implementation without knowing the difference between any two of them (Liskov Substitution Principle), and you don't need to be able to change StableDependency, then your snippet is perfectly fine.

Now, the DI purists might say that there should be no "stable" dependencies. If one class A depends on another class B, there is always a chance that B will have to be replaced with a C:B and thus you are going to want the flexibility to inject B so that you do not have to recompile A to make this change. In your case, by declaring StableDependency, you are asserting that you will never need a BetterStableDependency. Right or wrong, who knows; if you're right then the implementation never changes, but if you're wrong then the tightly-coupled implementation requires changes in more places.

When you use an IoC framework to automate your DI, you usually end up registering every dependency with the IoC no matter how likely it is to change; you have it, why not use it? So, I'd go ahead and register StableDependency with the IoC container as an IMaybeStableDependency.

2
  • 1
    I would say that such purists are failing hard at YAGNI. If the dependency does not need two different implementations now, than it's interface is most likely to change before it does and you'll need to refactor anyway.
    – Jan Hudec
    Commented Aug 14, 2012 at 12:49
  • A very good point; you can't close the system to all types of change, and a change to the interface IB of B before the introduction of a C:B or C:IB will require changing IB as well as A and B. But, it remains that tightly-coupled object instantiation violates the Open/Closed Principle.
    – KeithS
    Commented Aug 14, 2012 at 21:37
5

Context

The distinction between stable and volatile dependencies was an attempt on my part to provide guidance about 'what to inject and what not to inject.' It was based on my understanding of the problem space as of late 2000s.

It turns out that it's closely related to the Stable Dependencies Principle, which I believe I only later read about. When you arrive at a conclusion independently of other people who arrive at a similar conclusion, it strengthens the point. (This is, on the other hand, no guarantee: for centuries, people thought that the sun revolves around the Earth.)

Dependency Inversion

I'm not sure I explicitly stated it in the book, but dependencies are transitive. Thus, if you have, as you put it, a stable dependency that depends on a volatile dependency, that dependency graph contains volatility.

This is, however, the composition view. This describes how the graph looks at run time. As KeithS points out, according to the Liskov Substitution Principle, your StableDependency class should be able to accept any (correct) implementation of IVolatileDependency2. That implementation may or may not have to be volatile.

One problem that Dependency Injection attempts to address is the Stable Dependencies Principle. What if some fairly stable code depends on some other code that'll change at a higher frequency?

The way we address this dilemma is by applying the Dependency Inversion Principle, by saying that abstractions shouldn't depend on details, but rather that details should depend on abstractions.

So a consumer of a volatile dependency doesn't depend on the implementation detail; it depends on the abstraction (e.g. IVolatileDependency2). The abstraction should remain stable, while the implementation can remain volatile.

The implementation, likewise, depends on the abstraction, because it depends on the interface definition (e.g. IVolatileDependency2) in order to implement it. Again, the implementation detail depends on a stable contract.

I understand that you named the interfaces and classes in the OP as you did to illustrate a point, but in a real code base, the whole point is that you shouldn't care about what's behind an abstraction.

Specifics

Apart from all the theorising, the remaining question is: should you inject StableDependency into MyClass, or rather its constituent elements. I don't think that there's one single right answer to that question, but I recently answered a similar question on my blog. The discussion there doesn't involve make-believe types like StableDependency, MyClass, etc. so perhaps it'll be more illuminating.

Referential Transparency

I no longer think about 'what to inject, and what not to inject' in terms of stable and volatile dependencies. Instead, I distinguish between pure functions and impure actions.

An impure action is anything that has non-deterministic behaviour or involves side effects, so essentially all file I/O, database reads and writes, random number generation, sending of emails, web service calls, system clock usage, etc.

As that partial list suggests, there's quite a bit of overlap between impure actions and volatile dependencies.

The interpretation, though, differs. Referential transparency has stronger implications for transitivity, because any function that invokes an impure action must itself be impure. This has implications for architecture, but also for Dependency Injection. Essentially, if you inject an impure action, the dependent component is also impure.

I don't think that viewpoint is what you're ultimately asking about, but I wonder if you somehow got them mixed up, since the transitivity effect is much more pronounced in this alternative perspective.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.