Right now, HelloWorldCommand
uses System.out.println()
to write its output.
In the spirit of dependency injection, let’s use an abstraction so that we can
remove this direct use of System.out
. What we’ll do is create an Outputter
type that does something with text that’s written to it. Our default
implementation can still use System.out.println()
, but this gives us
flexibility to change that later without changing HelloWorldCommand
. For
example, our tests may use an implementation that adds the string to a
List<String>
so we can check what was output.
Here’s our Outputter
type:
interface Outputter {
void output(String output);
}
And here’s how we’d use it in HelloWorldCommand
:
private final Outputter outputter;
@Inject
HelloWorldCommand(Outputter outputter) {
this.outputter = outputter;
}
...
@Override
public Result handleInput(List<String> input) {
if (!input.isEmpty()) {
return Result.invalid();
}
outputter.output("world!");
return Result.handled();
}
Outputter
is an interface
. We could write an implementation of it, give that
class an @Inject
constructor, and then use @Binds
to bind Outputter
to
that implementation. But Outputter
is very simple … so simple that we could
even implement it as a lambda or method reference. So instead of doing all that,
let’s write a static
method that just creates and returns an instance of
Outputter
itself!
@Module
abstract class SystemOutModule {
@Provides
static Outputter textOutputter() {
return System.out::println;
}
}
Here we’ve created another @Module
, but instead of a @Binds
method we
have a @Provides
method. A @Provides
method works a lot like an
@Inject
constructor: here it tells Dagger that when it needs an instance of
Outputter
, it should call SystemOutModule.textOutputter()
to get one.
Again, we’ll need to add our new module to our component definition to tell Dagger that it should use that module for our application:
@Component(modules = {HelloWorldModule.class, SystemOutModule.class})
interface CommandRouterFactory {
CommandRouter router();
}
Once again, nothing has changed about the behavior of our application, but
it’s now easy to write unit tests for our command without causing it to actually
write to System.out
.
CONCEPTS
@Provides
methods are concrete methods in a module that tell Dagger that when something requests an instance of the type the method returns, it should call that method to get an instance. Like@Inject
constructors, they can have parameters: those parameters are their dependencies.@Provides
methods can contain arbitrary code as long as they return an instance of the provided type. They do not need to create a new instance on each invocation.
- This highlights an important aspect of Dagger (and dependency injection as a whole): when a type is requested, whether or not a new instance is created to satisfy that request is an implementation detail. Going forward, we’ll use the term “provided” instead of “created”, as that is more accurate for what is happening.