I'll use application configuration as the example. I'll uncreatively title the class of unnecessary dependencies: "The thing that gets the thing you need". And I'll uncreatively title the remedy to this problem: "Pass the thing you need, not the thing that gets the thing you need".
The config example
Most applications we write have a configuration defined via some XML or JSON file sitting near the executing code. We want to keep the system modular and testable so we make sure the application code doesn't depend directly on this configuration file. A common way we implement this looks similar to this:
Then when one of our application classes needs a value from the application's configuration, we give it a dependency on IConfigurationReader and let it read a config value in one of its methods. For this example, let's make a class that generates invoices. We have multiple environments and don't want to mistake test invoices for real ones. So we decide to put a watermark on invoices generated outside of production. One way to accomplish this would be to create a "invoiceWatermarkText" config setting that is set in test/staging and not set in production. Here's what this might look like:
This is SOLID, decoupled code. InvoiceGenerator rightly doesn't know about the details of how configuration works. And it's testable because you can stub out the configuration reader. However, there are still problems.
Loose coupling is still coupling
Although InvoiceGenerator doesn't know about how configuration works, it still has to know there is such a thing as configuration. This makes things more complicated. When you test, you have to provide a stub. And when you create an InvoiceGenerator in the production code, you have to provide a real configuration. InvoiceGenerator is still coupled to configuration, thus making it impossible to use it without supplying some sort of configuration reader.
Eliminate the coupling
We can improve this with a simple fix: Pass the thing we need (the watermark text), not the thing that gets the thing we need (the IConfigurationReader). It will look something like this:
Now we can painlessly use an InvoiceGenerator literally anywhere in any application. And there's less to think about when reasoning about it.
But...we still need the config part
You're probably wondering, "Ok, so I still need to read that text from config to give it to the InvoiceGenerator. Where does that code go?". You have a couple options here:
- Just read it from config on the line that is calling GenerateInvoice
- Create a one-line overload to GenerateInvoice that reads from the real configuration reader. No need to test that line.
Wrapping it all up
We often introduce a dependency so we can get the thing we need. However, we usually only need the thing we need. So we pass the thing we need, not the thing that gets the thing we need.