Version without JavaScript, read-only: workshops, quizzes and running code are not available here. Full version · Catalogue · Sign in
Lecture notes · chapter 5
Depending on an interface
Declaring an interface is not enough to reduce coupling: the class must receive the object it depends on instead of creating it itself.
This chapter extends sequence 3. It is short, but the idea it presents is often poorly applied.
Tight and loose coupling
Here is a trainer who can only train a mallard:
class Dresseur {
private CanardColvert canard;
Dresseur(CanardColvert c) { this.canard = c; }
void entrainer() { canard.voler(); }
}
To add a CanardEnPlastique, Dresseur has to be modified, and the same goes for a CanardRobot. Each new kind of duck requires a change to the Dresseur class, even though it has no need to know the different kinds of ducks.
Here is the loosely coupled version:
interface ICanard { void voler(); }
class CanardColvert implements ICanard {
public void voler() { System.out.println("Je vole !"); }
}
class Dresseur {
private ICanard canard;
Dresseur(ICanard c) { this.canard = c; }
void entrainer() { canard.voler(); }
}
The trainer can now train any duck, including those that will be added later, without its class having to be modified.
Declaring an interface is not enough
The following code does declare an interface, but the coupling is unchanged.
class Dresseur {
private ICanard canard;
Dresseur() {
this.canard = new CanardColvert(); // ← it builds it itself
}
}
The declared type is ICanard, but the class remains tied to CanardColvert: it knows its name, it instantiates it, and it cannot be given another duck. To test the trainer, you need a real mallard, and to use it with a robot, you have to modify its code.
The fix takes a single line: the new is moved to the caller.
class Dresseur {
private final ICanard canard;
Dresseur(ICanard canard) { this.canard = canard; } // injection
}
This is what is called dependency injection: in its simplest form, it is just a constructor parameter.
What injection brings
Replacement. A CanardRobot, a fake duck for tests or a fallback implementation can be used without modifying Dresseur.
Testing. A unit test can provide a fake duck that records that it was made to fly, without a database, a network or a real pond.
class CanardEspion implements ICanard {
boolean aVole = false;
public void voler() { aVole = true; }
}
CanardEspion espion = new CanardEspion();
new Dresseur(espion).entrainer();
// espion.aVole is true: the trainer did its job
The direction of dependencies. The trainer depends on an abstraction (being able to fly) rather than on an implementation. The concrete classes depend on the same abstraction, and no concrete class depends on another.
classDiagram
direction TB
class ICanard {
<<interface>>
+voler()
}
Dresseur --> ICanard : depends on
ICanard <|.. CanardColvert
ICanard <|.. CanardRobot
ICanard <|.. CanardEspionIn the diagram, all the arrows point to the interface and none to a concrete class.
Where to put the interface
An interface is justified when it corresponds to a role that several objects can play, or to an object you want to be able to replace.
Composition as a choice of behaviour
The same mechanism, applied inside a class, corresponds to the Strategy pattern, presented in chapter 10.
interface VolStrategy { void voler(); }
class VolerAvecAiles implements VolStrategy {
public void voler() { System.out.println("Je vole avec mes ailes !"); }
}
class NePasVoler implements VolStrategy {
public void voler() { System.out.println("Je ne vole pas."); }
}
class Canard {
private VolStrategy vol;
Canard(VolStrategy vol) { this.vol = vol; }
void effectuerVol() { vol.voler(); }
}
A CanardRobot simply receives NePasVoler, with no need for a subclass, an instanceof or an if.
Exam question
What must be done, besides declaring an interface type, for a class to be really replaceable?
The implementation must be passed to it from outside (through the constructor) instead of letting the class instantiate it itself. As long as the class contains new CanardColvert(), the declared type changes nothing.