Search |
||
Getter-based Dependency InjectionPosted by crazybob on May 26, 2004 at 2:41 PM PDT
Like service locators, dependency injection containers save you from implementing the same dynamic loading and configuration code over and over in different ways; you can configure your entire application consistently in one place. Unlike service locators, dependency injection containers don't require component dependence on a service locator API which facilitates sharing between teams that may have their own service locators. Cedric and I chatted last week about his "useless methods" blog and the benefits of getter-based dependency injection over setter or constructor-based injection (a la pico container and Spring). Cedric compares CMP entity beans with theoretical getter-based containers which extend classes at runtime and override getter methods to return injected dependencies. The overridden getters may be bean-style (for easier testing) or abstract (for a cleaner, field free implementation). Such a framework would not require classes to declare setter methods, constructors or fields. Getter injection better enables the container to lazy load dependencies and simplifies injecting different dependencies of the same type.
I implemented a simple proof of concept using dynaop which you can find in the dynaop.aspects.ioc package in the aspects directory in CVS. The API consists of a single class Container. The I've expanded on and slightly modified the example from the pico container Two-minute Tutorial. The original example has two components, Boy and Girl. The Girl depends on the Kissable interface which the Boy implements:
public interface Kissable {
public void kiss();
}
public class Boy implements Kissable {
boolean kissed;
public boolean isKissed() {
return kissed;
}
public void kiss() {
kissed = true;
}
}
public abstract class Girl {
public abstract Kissable getKissable();
public void kissEveryone() {
getKissable().kiss();
}
}
The pico container example passes the Kissable implementation to the Girl instance via the constructor. Using constructor-based injection, sans a lazy-loading stub, the Kissable instance must be created before the Girl instance. Also, things can get hairy if the Girl depends on two different Kissable implementations at the same time. In our example above (modified to use getter-based injection), the container will implement the abstract import dynaop.aspects.ioc.Container; ... // configure container Container c = new Container(); c.add(Boy.class); c.add(Girl.class); ... // look up and use Girl instance Girl girl = (Girl) c.get(Girl.class); // this returns an instance of Boy Kissable kissable = girl.getKissable(); Using getter-based injection, we can easily modify Girl to depend on two different Kissable instances by adding a differently named method. Using the configuration in our running example, each getter method will return a different instance of Boy:
public abstract class Girl {
public abstract Kissable getKissable();
public abstract Kissable getPrinceInTraining();
public void kissEveryone() {
getKissable().kiss();
getPrinceInTraining().kiss();
}
}
Let's create a second implementation of Kissable:
public class Frog implements Kissable {
boolean turnedIntoPrince;
public boolean isTurnedIntoPrince() {
return turnedIntoPrince;
}
public void kiss() {
turnedIntoPrince = true;
}
}
We can now configure
Container c = new Container();
c.add(Boy.class);
c.add("princeInTraining", Frog.class);
c.add(Girl.class);
Finally, we implement a JUnit test that verifies our assertions:
import dynaop.aspects.ioc.Container;
...
public void testInsatiableGirl() {
// create and configure IoC container
Container c = new Container();
c.add(Boy.class);
c.add("princeInTraining", Frog.class);
c.add(Girl.class);
// look up and invoke method on Girl instance
Girl girl = (Girl) c.get(Girl.class);
girl.kissEveryone();
// test that getKissable() returns instance of Boy
assertTrue(girl.getKissable() instanceof Boy);
assertTrue(((Boy) girl.getKissable()).isKissed());
// test that getPrinceInTraining() returns instance of Frog
assertTrue(girl.getPrinceInTraining() instanceof Frog);
assertTrue(((Frog) girl.getPrinceInTraining()).isTurnedIntoPrince());
}
From a testability standpoint, we can use the container directly in our test as in the example above. We could also provide concrete getter methods in our component class which the container will override at runtime. A third option is to create a MockGirl class that extends Girl. Almost any IDE can automatically override the abstract methods with default implementations, and we can provide test-specific implementations as needed using anonymous inner classes. In this particular situation, dynaop outshines both the OO and alternate AOP approaches in simplicity and performance. I used custom method pointcuts that pick methods based on the container configuration. The container determines at load time exactly which dependency resolution code if any to apply to methods. For example, the container only applies the interceptor that resolves dependencies by ID to methods whose name and return type correspond to a configured component.
You can apply your own custom aspects to the components by passing an »
Comments
Comments are listed in date ascending order (oldest first)
|
||
|
|