Dependency Injection
Dependency Injection is solving a fundamental problem in software architecture. How do I decouple an piece of code that does something from the code that does it?
Consider a service that downloads a file and computes the hash of it. One obvious way of doing that is simply calling the web services to download the file and the HashService to hash the file:
public class WebHashService {
public Hash getHash(Url url) {
String file = new WebService().download(url);
return new HashService().hash(file)
}
}However this approach has multiple drawbacks. First, it is hard to test. We cannot test it without downloading a file from the internet. Not only is this slow, but your test might break if the webpage changes! Second, it is hard to change. The file is closed for extension, and not closed for modification (see SOLID).
We should rely on abstractions not concretions. We can do that by injecting our dependencies instead of defining them in the file.
public class WebHashService {
// Define the Downloader interface
public static interface Downloader {
String download(Url url);
}
private final Downloader downloader;
// Define the Hasher interface
public static interface Hasher {
Hash hash(String url);
}
private final Hasher hasher;
// Now we can ask the caller to inject the dependencies.
public WebHashService(Downloader downloader, Hasher hasher) {
this.downloader = downloader;
this.hasher = hasher;
}
// We can call the abstract
public Hash getHash(Url url) {
String file = downloader.download(url);
return hasher.hash(file)
}
}Now testing the code is trivial:
@Test
void download_and_hash() {
// Mock a downloader
Downloader downloader = (Url url) -> {
assertEquals("google.com", url);
return "<web>"
}
// Mock a hasher
Hasher hasher = (String string) -> {
assertEquals("<web>", string);
return new Hash("thehash")
}
// Inject the dependencies
WebHashService ws = new WebHashService(downloader, hasher);
// Check that we get the right hash.
assertEquals(new Hash("thehash"), ws.getHash("google.com"));
}Doing this in your code is sometimes referd to as Dependency Inversion, because we are changing direction of the dependencies.
To read more take a look at