1

Many java DI frameworks (Spring, CDI amongst others) implements some kind of qualified injection through user-designed qualifiers annotations.

interface Engine { .. }

@Powerful
public PowerfulEngine implements Engine { .. }

public class Car {
    @Inject @Powerful
    private Engine engine;  // will inject a PowerfulEngine instance
}

And this is advertised as a way to enable loose coupling between the objects. But because one and only one @Powerful implementation will be allowed in the project, I don't understand how this is any less coupled than this:

public class Car {
    @Inject
    private PowerfulEngine engine;  
}

I can see how it would be vaguely nicer to mock the interface instead of an object when writing an unit test, the fact remains that you specify the exact kind of implementation you need in your injection.

Am I missing something ?

1 Answer 1

2

In your first example you are using an Engine attribute within your Car class. The Engine is exposed through an interface and as such you are depending on the interface declaration. Using the annotations you are asking for an implementation of the Engine interface named Powerful, to let them compiler know which exact implementation of the interface you want to use in this specific case, but the Java compiler still will not allow you to use any method outside the interface's declaration.

How is the first example less coupled?

If we consider a scenario where the PowerfulEngine only contains methods the Engine interface does, meaning the class itself does not add any own methods, the coupling is exactly the same in both cases, however once you start adding specific methods to the PowerfulEngine class and using them the situation is a whole lot different.

For explanation let's extend your example a little bit:

interface Engine
{
    void run();
}

class PowerfulEngine implements Engine
{
    @Override
    public void run() {
        System.out.println("Running a powerful engine.");
    }
}

class RegularEngine implements Engine
{
    @Override
    public void run() {
        System.out.println("Running a regular engine.");
    }
}

class Car
{
    private Engine engine;

    public void go() {
        engine.run();
    }
}

The Car class has got a go method which calls the run method of the Engine. Now depending on the class that has been injected into a Car object the system will either output Running a powerful engine. or Running a regular engine.

The Car class knows it can use the run method of the Engine because it provides it and does not care about its actual implementation. As I have mentioned, if you decide to replace the Engine dependency with the PowerfulEngine dependency, in this case nothing happens because you are still using only the method declared in the Engine interface.

But if you add another method to the PowerfulEngine class and use it, that's a whole lot different case.

Let's modify the PowerfulEngine and Car classes a little.

class PowerfulEngine implements Engine
{
    @Override
    public void run() {
        System.out.println("Running a powerful engine.");
    }

    public void rev() {
        System.out.println("Revving a powerful engine.");
    }
}

class Car
{
    private PowerfulEngine engine;

    public void go() {
        engine.rev();
        engine.run();
    }
}

What would now happen if you decided to use a RegularEngine instead of the PowerfulEngine? Your application would fail during compilation because the RegularEngine class does not have the rev method, neither does the Engine interface. You would be accessing a method which does not exist.


All in all, if you are depending on a concrete class and are only using methods available in a certain interface the class implements, it's better to rely on the interface rather than on the class directly, thus letting others explicitly know: Hey, I am not actually depending on the implementation, I don't care about it, I only care that the interface has these contracts pesented through methods and I can use them.

If you need to use a specific method of a class which is not published through any abstraction then you have no other option than to rely on a concrete implementation.

1
  • Thank you for the answer. I think you're right, and that's the idea behind these features. I'm still a bit troubled by the concept of 1) making static, prior assumptions of the exact type being injected, while 2) forbidding the concrete use of its specifics. But yes, from a future-proofing point of view, i guess it makes sense.
    – fabienbk
    Commented Feb 16, 2017 at 13:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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