Injection de dépendance
(Dependency injection)

On est dans une situation où un objet "de haut niveau" (le client) a besoin d'utiliser un objet "de plus bas niveau" (un service ou un composant logiciel).
On sépare l'utilisation du service de sa configuration.
On parle d'injection de dépendance lorsque ce n'est pas le client qui choisit quel service utiliser.
Sources : martinfowler.com

Sans injection de dépendance

C'est le client qui décide quel service utiliser.
// ********** client **************
class Client1{

    private ServiceI service;

    public Client1() {
        service = new Service1(); // ICI le client choisit
    }

    public void action(){
        System.out.println("Client.action() : " + service.doSomething());
    }
}

// ********** service **************
interface ServiceI{
    public String doSomething();
}

class Service1 implements ServiceI{
    public String doSomething(){
        return this.getClass() + ".doSomething()";
    }
}

// ********** main **************
public class Test1{
    public static void main(String[] args){
        Client1 client = new Client1();
        client.action();
    }
}

Problèmes

Cette solution fonctionne, mais elle est rigide car le client est lié à une implémentation donnée du service.
De plus, il n'est pas possible de tester le client indépendamment du service. Injection de dépendance Dans un vrai système, on peut avoir des dizaines de services, avec plusieurs implémentations possibles par service, il faut que le client puisse fonctionner avec n'importe quelle implémentation du service.
On aussi a parfois besoin de pouvoir déployer des clients utilisant des implémentations différentes des services utilisés.

On cherche à se retrouver dans une situation de plugin (des programmes complètement indépendants qui fonctionnent ensemble).

Deux patterns répondent à cette problématique : dependency injection et service locator.

On parle d'inversion de contrôle, car le client ne décide plus de l'implémentation des composants qu'il utilise.

Injection de dépendance

On définit habituellement 3 types d'injection : Dans tous les cas, c'est du code extérieur au client qui décide de l'implémentation des services utilisés par le client. Injection de dépendance Le client est déconnecté de l'implémentation.

Injection par constructeur (type 3)

Le service à utiliser est "injecté" dans le client par le biais d'un constructeur.
// ********** client **************
class Client2{

    private ServiceI service;

    public Client2(ServiceI service) {
        this.service = service; // ICI injection
    }

    public void action(){
        System.out.println("Client.action() : " + service.doSomething());
    }
}

// ********** service **************
interface ServiceI{
    public String doSomething();
}

class Service2 implements ServiceI{
    public String doSomething(){
        return this.getClass() + ".doSomething()";
    }
}

// ********** main **************
public class Test2{
    public static void main(String[] args){
        Client2 client = new Client2(new Service2());
        client.action();
    }
}

Intérêts

Inconvénients

Injection par setter (type 2)

Le client peut être créé sans service, et le service lui est injecté par le biais d'un setter.
// ********** client **************
class Client3{

    private ServiceI service;

    public void setService(ServiceI service){
        this.service = service; // ICI injection
    }
    
    public void action(){
        System.out.println("Client.action() : " + service.doSomething());
    }
}

// ********** service **************
interface ServiceI{
    public String doSomething();
}

class Service3 implements ServiceI{
    public String doSomething(){
        return this.getClass() + ".doSomething()";
    }
}

// ********** main **************
public class Test3{
    public static void main(String[] args){
        Client3 client = new Client3();
        client.setService(new Service3());
        client.action();
    }
}

Injection par interface (type 1)

Même chose que l'injection par setter, mais on demande en plus au client d'implémenter une interface.
// ********** client **************
interface ServiceUser{
    public void injectService(ServiceI service);
}

class Client4 implements ServiceUser{

    private ServiceI service;

    public void injectService(ServiceI service){
        this.service = service; // ICI injection
    }
    
    public void action(){
        System.out.println("Client.action() : " + service.doSomething());
    }
}

// ********** service **************
interface ServiceI{
    public String doSomething();
}

class Service4 implements ServiceI{
    public String doSomething(){
        return this.getClass() + ".doSomething()";
    }
}

// ********** main **************
public class Test4{
    public static void main(String[] args){
        Client4 client = new Client4();
        client.injectService(new Service4());
        client.action();
    }
}

Remarques

Service locator

L'idée est d'avoir un objet (le service locator) qui sait comment créer tous les services dont une application (un client) peut avoir besoin.
Le diagramme exprimant les dépendances devient le suivant : Service Locator Exemple de JNDI en JEE.