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.