10

I recently read about the @ImplementedBy annotation available in Google Guice. It allows the programmer to specify a binding between an interface and its implementation for future use in dependency injection. It is an example of a just-in-time binding.

I'm quite used to defining explicit bindings in my modules, using the following syntax:

bind(SomeInterface.class).to(SomeInterfaceImplementation.class);

According to the documentation, this is equivalent to the following use of the @ImplementedBy annotation:

@ImplementedBy(SomeInterfaceImplementation.class)
public interface SomeInterface {
    //method declarations
}

The only gain I can see here is that the code is marginally shorter. At the same time, this approach has a drawback rightly pointed out by the same docs:

Use @ImplementedBy carefully; it adds a compile-time dependency from the interface to its implementation.

Such dependency may not be a problem in many cases but I personally see it as a code smell.

What use cases make the @ImplementedBy annotation worth using?

One possible way seems to be to employ it in the code of a library or framework. As described in the docs, the annotation can provide a default binding easily overriden by an explicit one.

If a type is in both a bind() statement (as the first argument) and has the @ImplementedBy annotation, the bind() statement is used. The annotation suggests a default implementation that can be overridden with a binding.

This way, as the developer of a library, I can provide my users with an out of the box binding that can be customized somewhere in the client code.

Is this the only reason for the annotation to exist? Or is there something I'm missing? Can I gain anything by using it in code that is just an application taking care of some business logic and not a library/framework to be extended?

8

I think the danger here is using only the @ImplementedBy annotation. Used appropriately, in conjunction with bind() statements in your module and so forth, it is okay.

Having a default implementation is great for testing; you don't necessarily want to have to explicitly define a mock injection every time you are testing a class that has lots of dependencies, or if you have a class that many things are dependent on (so you have to define a mock every single time).

For example, you might have a class:

@ImplementedBy(NoOpDataService.class)
interface DataService {
    Map<String, MyPOJO> getData();
}

And then NoOpDataService is:

class NoOpDataService implements DataService {
    @Override
    public Map<String, MyPOJO> getData() {
        return Collections.emptyMap();
    }
}

You'll never use this in your actual code, obviously; in your Guice module you would bind an implementation that actually does something. But all tests on classes that get an injected DataService don't need to have a mock binding anymore.

tl;dr I agree with you that having your interfaces depend on your implementation can be a code smell; but it can also remove boilerplate code make testing easier. It's not a difficult to implement feature; and while there is some small potential for abuse, ultimately the consequences cannot be too bad (a service starts up by surprise), and it would not be too hard to fix even if it does happen.

| improve this answer | |
  • 3
    Adding test code to production? – Basilevs Dec 26 '15 at 16:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.