Dependency Injection
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.
- GWT
- Vagrant und Chef