17

Given a constructor that will never, ever, have to use any different implementations of several objects that it initializes, is it still practical to use DI? After all, we might still want to unit test.

The class in question initializes a few other classes in its constructor and the classes it uses are pretty specific. It will never use another implementation. Are we justified in avoiding trying to program to an interface?

Seph
  • 279

8 Answers8

15

It depends on how certain you are that "never, ever" is correct (during the time your app will be used).

In general:

  • Don't modify your design just for testability. Unit tests are there to serve you, not vice versa.
  • Don't repeat yourself. Making an interface that will only ever have one inheritor is useless.
  • If a class isn't testable, it's likely not flexible/extensible for real use cases either. Sometimes that's fine - you're unlikely to need that flexibility, but simplicity/reliability/maintainability/performance/whatever is more important.
  • If you don't know, code to the interface. Requirements change.
Telastyn
  • 110,259
12

Are we justified in avoiding trying to program to an interface?

While the advantage of coding against an interface is pretty evident, you should ask yourself what exactly is gained by not doing it.

The next question is: is part of the responsibility of the class in question to choose the implementations or not? That may or may not be the case and you should act accordingly.

Ultimately, there's always potential for some goofy constraint to come out of the blue. You may want to run the same piece of code concurrently, and the possibility of injecting the implementation might help with synchronization. Or after profiling your app you may discover you want an allocation strategy different from plain instantiation. Or cross-cutting concerns come up and you don't have AOP support at hand. Who knows?

YAGNI suggest that when writing code, it's important not to add anything superfluous. One of the things that programmers tend to add to their code without even being aware of it, are superfluous assumptions. Like "this method might come in handy" or "this'll never change". Both add clutter to your design.

back2dos
  • 30,140
7

It depends on various parameters but here are a few reasons why you could still want to use DI:

  • testing is quite a good reason in itself I believe
  • the classes required by your object might be used in other places - if you inject them it enables you to pool the resources (for example if the classes in question are threads or database connections - you don't want each of your classes to create new connections)
  • if initialising those other objects can fail, code higher up the stack will probably be better at dealing with problems than your class which will then probably simply forward the errors

Bottom line: you can probably live without DI in that case but there are chances that using DI will end up in cleaner code.

assylias
  • 1,207
3

When you design a program or a feature, part of the thought process should be how you are going to test it. Dependency Injection is one of the testing tools, and should be considered as part of the mix.

The additional benefits of Dependency Injection and using interfaces instead of classes are also beneficial when an application is extended, but they can also be retrofitted to the source code later quite easily and safely using search and replace. - even on very large projects.

Michael Shaw
  • 10,114
2
 > Dependency Injection worth it **outside** of UnitTesting?
 > Are we justified in avoiding trying to program to an interface?

Many answers to this question can be debated as "you might need it because .." as YAGNI if you do not want to do unittesting.

If you are asking what reasons beside unittests require to program to an interface

Yes, Dependency Injection worth it outside of UnitTesting if you need inversion of control. For example if one module implementation need an other module that is not accessable in its layer.

Example: if you have the modules gui => businesslayer => driver => common.

In this scenario businesslayer can use driver but driver can not use businesslayer.

If driver needs some higherlevel functionality you can implement this functionality in businesslayer which implements an interface in common layer. driver only needs to know the interface in common layer.

k3b
  • 7,621
1

Yes you are - firstly, forget about unit testing as a reason to design your code around the unit test tools, it is never a good idea to bend your code design to fit an artificial constraint. If your tools force you to do this, get better tools (eg Microsoft Fakes/Moles that allow you many more options for creating mock objects).

For example, would you split your classes up into only-public methods just because the test tools do not work with private methods? (I know the prevailing wisdom is to pretend you don't need to test private methods, but I feel this is a reaction to the difficulty in doing so with current tools, not a genuine reaction to not needing to test privates).,

All in all it comes down to what kind of TDDer you are - the "mockist" as Fowler describes them, need to change the code to suit the tools they use, whereas the "classical" testers create tests that are more integration in nature (ie test the class as a unit, not each method) so there is less need for interfaces, especially if you use the tools that can mock concrete classes.

gbjbaanb
  • 48,749
  • 7
  • 106
  • 173
1

Dependency Injection also makes it clearer that your object HAS dependencies.

When someone else goes to use your object naively (without looking at the constructor), they will find that they will need to set up services, connections, etc. That were not obvious because they didn't have to pass them in to the constructor. Passing in the dependencies makes it clearer what is needed.

You are also potentially hiding the fact that you class may be violating SRP. If you are passing in many dependencies (more than 3), your class may be doing too much and should be refactored. But if you are creating them in the constructor, this code smell will be hidden.

Schleis
  • 3,416
  • 1
  • 21
  • 21
0

I agree with both camps here and the matter is still open for debate in my opinion. YAGNI demands that I not tackle changing my code to fit supposition. Unit testing is not a problem here because I firmly believe that the dependency will never, ever change. But if I was Unit testing as intended to begin with, I would have never reached this point anyway. As I would have slimed my way into DI eventually.

Let me offer another alternative for my scenario specifically

With a factory pattern I can localize dependencies in one place. When testing I can change implementation based on context, and that's good enough. This doesn't go against YAGNI because of the benefits of abstracting several class constructions.

Seph
  • 279