by Miško Hevery
We talked about how it is important to separate the new operators from the application logic . This separation forces your code to have factories which are responsible for wiring your application together. By separating these responsibilities the tests can always wire together a subset of an application with key components replaced for friendlies making testing easier and more focused.
Let's look at a sample factory
class CarFactory {
Car create() {
return new Car(
new EngineCompartment(
new Engine(),
new Door(new PowerWindow()),
new Door(new PowerWindow()),
new PowerSeat(), new PowerSeat() new ManualTransmission(),
new PowerSteering(),
new Battery()
),
new Cabin(
new Door(new PowerWindow()),
new Door(new PowerWindow()),
new PowerSeat(), new PowerSeat()
),
Arrays.asList(
new Wheel(new Tire(), new Rim()),
new Wheel(new Tire(), new Rim()),
new Wheel(new Tire(), new Rim()),
new Wheel(new Tire(), new Rim())
)
);
}
} This factory builds a car. The first thing to notice is that all of the new operators are here (If you were to look inside each of the classes it would be devoid of "new"s). The second thing to notice is a complete lack of logic (no loops or conditions). And thirdly, your application behavior is controlled by the way the classes are wired together. If I wanted a automatic-transmission car, all I would have to do is to wire the classes differently. The wiring responsibility is in the factory class not with the application logic.
But why do we need to tell the JVM how to wire these Classes together? Is it not self obvious? Just look at the constructors of these classes:
Car(EngineCompartment ec, Cabin c, List ws);
EngineCompartment(Engine e, Transmission t,
Steering s, Battery b);
Cabin(Door driverDoor, Door passengerDoor,
Seat driverSeat, Seat passengerSeat);
Engine(float dissplacement, int pistonCount);
Battery(float voltage);
Door(Window window);
new Door(new PowerWindow()),
new Door(new PowerWindow()),
new PowerSeat(), new PowerSeat() PowerWindow() implements Window;
PowerSeat() implements Seat;
Wheel(Tire tire, Rim rim);
... Imagine you could just ask for things. Lets start simple and look at the Wheel. The constructor of Wheel needs a Tire and Rim. So when we ask for a Wheel it should be obvious that we want new Wheel(new Tire(), new Rim()) . Why do we need to make this explicit in our factory? Lets build a framework from which we can ask for a class and it returns an instance of that class. So in our case if we ask for getInstance(Wheel.class) it returns a new Wheel(new Tire(), new Rim()) . Now a framework like this is easy to build since all we need to do is look at the constructor and recursively try to instantiate the objects until all recursive constructors are satisfied.
But things are a bit more complicated than that. What if we ask for Cabin, as in getInstance(Cabin.class) ? Well Cabin needs two Doors and two Seats, but Seat is an interface so we have to make a decision: What subclass of Seat should we instantiate? To help our framework make that decision, somewhere we will add a bind method such as bind(Seat.class, PowerSeat.class) . Great! Now when we call getInstance(Seat.class) the framework returns new PowerSeat() . Similarly, we will have to call bind(Window.class, PowerWindow.class) . Now we can call getInstance(Cabin.class) and the framework will return new Cabin(new Door(new PowerWindow()), new Door(new PowerWindow()), new PowerSeat(), new PowerSeat()) .
Notice that closer a class you ask for is to the root (Car in this case), the more work will the framework do for us. So ideally we just want to ask for the root object, Car. Calling getInstance(Car.class) will cause the framework to do all of the work originally in our factory.
As you can see a framework which will call the new operators on your behalf is very useful. This is because you only have to ask for the root object (in our case the Car) and the framework will build the whole object graph on your behalf. This kinds of frameworks are called Automatic Dependency Injection frameworks and there are few of them our there. Namely GUICE , PicoContainer , and Spring .
Since I know most about GUICE , The above example can be rewritten in GUICE like this:
class CarModule extends AbstractModule() {
public void bind() {
bind(Seat.class, PowerSeat.class);
bind(Seat.class, PowerSeat.class);
bind(Transmission.class, ManualTransmission.class);
// maybe use a provider method?(jwolter)
// maybe explain the need for different wheels, so use a Provider;
bind(new TypeLiteral>(){})
.toProvider(new Provider>(){
@Inject Provider wp;
List get() {
return Array.asList(wp.get(), wp.get(),
wp.get(), wp.get());
}
});
}
// or, what I think is simpler and clearer
@Provides
List provideWheels(Provider wp) {
return Array.asList(wp.get(), wp.get()
wp.get(), wp.get());
}
}
// Then somewhere create your application, using the injector only
// once, for the root object.
Injector injector = Guice.createInjector(new CarModule());
Car car = injector.getInstance(Car.class);
As you can see Automatic Dependency Injection frameworks can do a lot of things for you. Namely, that you don't have to worry about writing the factories. You simply declare dependencies, and write your application logic. As needed, you ask for your dependencies in a constructor and let the framework resolve all of them for you. You move the responsibility of calling the new operator to the framework. The DI-framework is your new "new". Now DI-frameworks can do lot of other things, which are beyond the scope of this article, such as manage object lifetimes, enforce singletons (in a good way, see: Root Cause of Singletons and Singletons are Pathological Liars ) and manage different configurations of your application such as production vs development server.
For a more real life example of a Guice Module see: CalculatorServerModule.java
Also, the curious may wonder why a Provider is injected into the provider method for List<Wheel>. This is because our 4 wheeled car needs to have different wheels. If we injected a single wheel, it would be assigned as the same wheel on all 4 positions. Providers, when used without any explicit scopes will return a new instance every time get() is called on them.
Very good article. When someone is learning how to first program and you break it down with real-world objects they already understand, like cars, it makes it a great deal easier. Why doesn't Google, who has some of the best engineers on the planet, release programming books under the Google brand?
ReplyDeleteIt seems your call to new engine compartment() suffers from cut and paste errors. I don't think it should include the doors and seats. In any case, I enjoy your articles.
ReplyDelete