SOLID- Dependency Inversion Principal
In this post, we are focusing on the “D” – Dependency Inversion Principle. Uncle Bob tells us: “Depend on abstractions, not on concretions.” This is sometimes referred to as Inversion of Control. In 2004, Martin Fowler referred to it as Dependency Injection
What’s Dependency Injection?
Let’s take a look at a definition of Inversion of Control. Uncle Bob Martin says there are three aspects of a bad design:
- Rigidity – hard to change because of the impact on every other part of the system.
- Fragility – changes break other parts of the system.
- Immobility – reuse elsewhere is hard because of entanglement between components of the system
Inversion of Control addresses these three issues using Uncle Bob’s simple approach: Depend on abstractions, not on concretions. In the world of Java, that means relying on interfaces and not implementation
The World Before Dependency Injection
Let’s take a look at a counter example:
// DON'T DO THIS
public class BadExample {
public static void main(String[] args) {
new ComputerProcessor()
.addComputer(new Doubler())
.addComputer(new Squarer())
.computeAll(8);
}
static class ComputerProcessor {
private List computers = new ArrayList();
public ComputerProcessor addComputer(Object o) {
computers.add(o);
return ComputerProcessor.this;
}
public void computeAll(long value) {
for (Object o : computers) {
long computedValue = -1;
if (o instanceof Doubler) {
computedValue = ((Doubler) o).computeDouble(value);
} else if (o instanceof Squarer) {
computedValue = ((Squarer) o).computeSquare(value);
}
String name = o.getClass().getSimpleName();
System.out.println("Computer: " + name + ", value: " + value + " computed value: " + computedValue);
}
}
}
static class Doubler {
public long computeDouble(long value) {
return value*2;
}
}
static class Squarer {
public long computeSquare(long value) {
return value*value;
}
}
}
The ComputerProcessor
is very fragile in this example. If we add a new class called Cuber
, and we want to be able to represent it in ComputerProcessor
, we need another if statement and more casting in order to call its computeCube
method.
Here’s the same example using Inversion of Control:
public class IoCExample {
public static void main(String[] args) {
new ComputerProcessor()
.addComputer(new Doubler())
.addComputer(new Squarer())
.computeAll(8);
}
static class ComputerProcessor {
private List<Computer> computers = new ArrayList<>();
public ComputerProcessor addComputer(Computer c) {
computers.add(c);
return ComputerProcessor.this;
}
public void computeAll(long value) {
for (Computer c : computers) {
String name = c.getClass().getSimpleName();
System.out.println("Computer: " + name + ", value: " + value + " computed value: " + c.compute(value));
}
}
}
interface Computer {
long compute(long value);
}
static class Doubler implements Computer {
public long compute(long value) {
return value*2;
}
}
static class Squarer implements Computer {
public long compute(long value) {
return value*value;
}
}
}
The enabling technology here is the Computer
interface. Now, ComputerProcessor
only deals with objects that conform to the Computer
interface. If we want to add a new Cuber
class, all it has to do is implement the Computer
interface. Nothing need change in ComputerProcessor
to deal with Cuber
objects.
Dependency Injection takes this concept and systematizes it in a way that makes it even easier to interact with your interfaces. Throughout this post, I will be using a Spring Boot example to demonstrate Dependency Injection. Since 2003, Dependency Injection has been a core feature of the Spring framework. The source code can be found here
Dependency Injection, Spring Style
@Autowired
annotation for dependency injection. Any of the Spring components can be autowired. These include, components, configurations, services and beans. We’ll look at a few of these in detail below.It’s a common pattern for controllers to be responsible for managing requests and responses while services perform business logic. Let’s look at our GreetingService
:
@Service
annotation makes it autowireable. Spring injects the dependency into our controller. If I set a breakpoint, I can see the implementation class backing greetingService
:Together, this setup adheres to the Dependency Inversion Principle. If the internals of the implementation class change, we don’t need to touch HomeController
.
Now, let’s say I want to support another GreetingService
implementation. Say, FrenchGreetingService
:
HomeController
. However, it does have a number of powerful annotations to deal with this. Let’s say that I want to inject the language specific version of the greeting service based on a property setting. We can use a Configuration
to tell Spring Boot which Service we want.On line 1, we are telling Spring Boot that this is a @Configuration
. That causes this class to be instantiated and the beans defined within to (potentially) be exposed for use throughout the Spring ecosystem. I’ve also removed the @Service
annotation from the EnglishGreetingService
and FrenchGreetingService
classes as it’s now this @Configuration
‘s responsibility to instantiate and expose the services as beans.
The @Bean
tells Spring Boot to expose a GreetingService
(this is how it is able to be autowired into our HomeController
). But, we are defining both beans with the same return type. That doesn’t seem right. The @ConditionalOnProperty
annotation ensures that only one of these beans will be injected. It’s looking for the language.name
property. Notice that there’s a matchIfMissing
parameter in the case of the EnglishGreetingService
. By default, that property is false. By setting it to true here, we are indicating to Spring Boot that if it doesn’t find a language.name
property, that EnglishGreetingService
is the default.
Let’s look at this in action. One thing to note is that Spring Boot automatically converts environment variables that are in all-caps with underscores to lowercase, dotted properties. Also, the examples below use HTTPie (https://github.com/jkbrzt/httpie), an alternative to curl. Curl will work as well. Try executing the following after doing mvn clean install
from your command line:
Also English
French
Important Note: Spring has very deep support for internationalization that the above example is not taking advantage of. The example is meant for demonstration purposes only.
Well, we jumped right into the deep end of Dependency Injection (DI) with Spring. Let’s take a step back and look at the different types of DI supported by Spring
Comments
Post a Comment