SOFTWAREENTWICKLUNG IN BERLIN
beratung, konzeption und umsetzung

Dependency Injection

27. Juli 2012 by Philip Sommer


Der Begriff Dependency Injection taucht schon seit einigen Jahren immer wieder in der Softwareentwicklung mit Java auf und hat sich mittlerweile als Standard-Praxis für die Entwicklung komplexer Anwendungen und Webanwendungen mit Java etabliert. Dependency Injection ist die Lösung für eines der Grundprobleme der Softwareentwicklung, nämlich dem aufwendigen Instanziieren von Objekten, welche in Abhängigkeiten zueinander stehen. Dies war nicht nur aufwendig, höchst komplex und kaum wartbar, nicht selten verlor der Entwickler schnell die Übersicht und an einen nachträglichen, unkomplizierten Austausch von Komponenten war kaum zu denken.

Dependency Injection löst diese Problematik durch loose Kopplung der Komponenten und sorgt ebenfalls für eine hohe Testbarkeit der einzelnen Klassen mit Mocking-Frameworks wie Mockito und Standardwerkzeugen wie JUNIT. Guice löst diese Situation sehr komfortabel und leichtgewichtig. Eine Klasse erstellt die benötigten Referenzen auf andere Objekte nicht selbst, sondern bekommt sie von Guice bereitgestellt. Dies kann über den Konstruktor oder alternativ über Field -oder Setter-Injection erfolgen. Dadurch ergibt sich die komfortable Situation, dass eine Komponenten, welche plötzlich bei der Implementierung einer Klasse benötigt wird, lediglich mit in den annotierten Konstruktor mit aufgenommen werden muss und schon sorgt Guice dafür, dass diese zur Laufzeit bereitgestellt wird.

Ich möchte hier ein Beispiel geben, welches auch auf den Seiten von Guice zu finden ist und verdeutlich, wie Dependency Injection funktioniert:


public interface BillingService {
    Receipt chargeOrder(PizzaOrder order, CreditCard creditCard);
}

Hier wird ein einfaches Interface erstellt, welches für welches es dann verschiedene Implementierungen geben mag. Welche Implementierung von Juice injected wird, bestimmt man durch ein Binding (siehe weiter unten).


class RealBillingService implements BillingService {
    private final CreditCardProcessor processor;
    private final TransactionLog transactionLog;

    @Inject
    RealBillingService(CreditCardProcessor processor,
    TransactionLog transactionLog) {
        this.processor = processor;
        this.transactionLog = transactionLog;
    }

    @Override
    public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard) {
        // here's my funny method
    }
}

Hier wird der Implementierung RealBillingService über Constructor-Injection eine Instanz von CreditCardProcessor und von TransactionLog injected. Dieses sind beide wiederum Interfaces und werden im folgenden Code an eine Implementierung gebunden.


public class BillingModule extends AbstractModule {
    @Override
    protected void configure() {
        /*
        * This tells Guice that whenever it sees a dependency on a TransactionLog,
        * it should satisfy the dependency using a DatabaseTransactionLog.
        */
        bind(TransactionLog.class).to(DatabaseTransactionLog.class);

       /*
        * Similarly, this binding tells Guice that when CreditCardProcessor is used in
        * a dependency, that should be satisfied with a PaypalCreditCardProcessor.
        */
        bind(CreditCardProcessor.class).to(PaypalCreditCardProcessor.class);
    }
}

Möchte man die Implementierungen austauschen, beispielsweise, weil man statt dem PaypalCreditCardProcessor eine neue Implementierung von CreditCardProcessor.class benutzen möchte, kann man dies hier einfach und unkompliziert tun.


public static void main(String[] args) {
   /*
    * Guice.createInjector() takes your Modules, and returns a new Injector
    * instance. Most applications will call this method exactly once, in their
    * main() method.
    */
    Injector injector = Guice.createInjector(new BillingModule());

    /*
    * Now that weve got the injector, we can build objects.
    */
    RealBillingService billingService = injector.getInstance(RealBillingService.class);
}

Mittels Guice.createInjector wird ein Injector instanziiert, welche dafür sorgt, dass in der Applikation die Abhängigkeiten verwaltet werden und in die richtigen Implementierungen der Interfaces in die Klassen injected werden. Dies ist nicht nur einfach und leicht wartbar, sondern erhöht auch die Testbarkeit der Java Applikation enorm.