Is it OK for code that gets a dependency by dependency injection to use a default implementation of the dependency unless told otherwise?

StackOverflow https://stackoverflow.com/questions/23419034

Question

Suppose my production code starts off like this:

public class SmtpSender
{
    ....
}

public void DoCoolThing()
{
    var smtpSender = new SmtpSender("mail.blah.com"....);
    smtpSender.Send(...)
}

I have a brainwave and decide to unit test this function using a fake SmtpSender and dependency injection:

public interface ISmtpSender
{
...
}

public class SmtpSender : ISmtpSender
{
...
}

public class FakeSmtpSender : ISmtpSender
{
...
}

public void DoCoolThing(ISmtpSender smtpSender)
{
    smtpSender.Send(...)
}

Then I think wait, all my production code will always want the real one. So why not make the parameter optional, then fill it in with default SmtpSender if not provided?

public void DoCallThing(ISmtpSender smtpSender = null)
{
    smtpSender = smtpSender ?? new SmtpSender("...");
    smtpSender.Send(...);
}

This allows the unit test to fake it while leaving the production code unaffected.

Is this an acceptable implementation of dependency injection?

If not, what are the pitfalls?

Was it helpful?

Solution

No, having callers depend on concrete implementations of dependencies is not the best way to do dependency injection. Component implementations should always depend only on component interfaces, not on other component implementations.

If one implementation depends on another,

  • It gives the caller a completely different kind of responsibility than it would otherwise have, that of configuring itself. The caller will thus be less coherent.
  • It gives the caller a dependency on a class that it would not otherwise have. That means that tests of the caller can break if the dependency is broken, classes needed by the dependency are loaded when the caller is loaded instead of when they're needed, and in compiled languages there would be a compilation-time dependency. All of these make developing large systems harder.
  • If more than one caller wants the same dependency, each such caller might use this same trick. Then you would have duplication of the concrete dependency among callers, instead of having the concrete dependency in one place (wherever you're specifying all your injected dependencies).
  • It prevents you from having a useful kind of circular dependency among implementations. Very occasionally I've needed implementations to depend on one another in a circular way. Pure dependency injection makes that relatively safe: implementations always depend on interfaces, so there isn't a compile-time circularity. (Dependency injection frameworks make it possible to construct all the implementations despite the circularity by interposing proxies.) With a concrete dependency, you'll never be able to do that.

I would address the issue you're trying to address by putting the production choice of implementation in the code or config file or whatever that specifies all of your implementations.

OTHER TIPS

It's a valid approach to use, but needs to be strictly limited to certain scenarios. There are several pitfalls to watch out for.

Pros:

  • Establishes a seam in your code, allowing different implementations to be used as collaborators
  • Allows clients to use your code without having to specify a collaborator - particularly if most clients would end up using the exact same collaborator

Cons:

  • Introduces a hidden collaborator into the system
  • Tightly couples the SUT to the concrete class
    • Even though other implementations could be provided, if the concrete class changes it will directly affect the SUT (e.g., the constructor changes)
  • Can easily tightly couple the SUT to other classes as well
    • For example: not only do you create the dependency, you create all of its dependencies too

In general, it's something you should only reserve for very lightweight defaults (think Null Objects). Otherwise it's very easy for things to quickly spin out of control.

What you're trying to do is similar to using a default constructor in order to make things a little more convenient. You should determine if the convenience is going to have long term benefits, or if it will ultimately come back to haunt you.

Even though you're not using a default constructor, I think you should consider the parallels. Mark Seemann suggests that default constructors could be a design smell: (emphasis mine)

Default constructors are code smells. There you have it. That probably sounds outrageous, but consider this: object-orientation is about encapsulating behavior and data into cohesive pieces of code (classes). Encapsulation means that the class should protect the integrity of the data it encapsulates. When data is required, it must often be supplied through a constructor. Conversely, a default constructor implies that no external data is required. That's a rather weak statement about the invariants of the class.

With all of this in mind, I think the example that you provided would be a risky approach unless the default SMTP sender is a Null Object (for example: doesn't actually send anything). Otherwise there is too much information hidden from the composition root, which just opens the gate to unpleasant surprises.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top