Assembly line: Separation of concerns with Inversion of Control.
Daniel, February 1st, 2008If you decide to go the route of using Inversion of Control and Dependency Injection, it can make your program a lot more flexible. If you do this manually, you’ll find that even a complex system can be managed well this way.
A common idiom is to use Dependency Injection to separate the creation of the object from the use of the object. The client object has some way to receive other objects it needs to “talk” to, but the client object doesn’t care how they were created:
class MyClient {
MyFooService fooService;
MyBarService barService;
public MyClient(MyFooService fooService, MyBarService barService) {
this.fooService = fooService;
this.barService = barService;
}
public void doStuff() {
fooService.iLikeFooFighters();
barService.buyRoundsForEveryone();
}
}
From this code snippet, FooService and BarService could be interfaces, abstract classes, or even concrete classes. If you change your mind later about them, MyClient doesn’t have to change at all
Another common approach for DI is to use setter based injection. This approach is useful for complex object graphs that have cyclic dependencies. I’ll leave it as an exercise for the reader to re-write MyClient with setters.
So, in this phase one, you’ve separated the concern of object creation and wiring from object usage. It is perfectly acceptable to stop here. However, depending on how complicated your object system is, you may wish to separate the concerns even further. You can do this via the Builder pattern (nb. The current Wikipedia entry on Builder pattern is incorrect and misleading.)
With the builder pattern, you separate the concern of creation from the concern of “wiring”. The builder accepts a series of objects that belong in a graph. The builder knows how to connect the objects through DI, but doesn’t care where the objects came from or how they were created/configured.
In a recent project of mine, I had to create a Robot instance, and wire it with a lot of components. To make things worse, some of the components also needed sub-components, and some components needed to know about other components, and some needed to know about the robot itself. I had successfully created a class that would create the components and wire them together, but it was a bit fragile (do things in the wrong order and you get an NPE, since not all of the components were created yet).
To solve this design issue, I created a class (HardwareContext), which held a reference to every component that it takes to build a robot. Those fields were populated using setter based dependency injection. Once those references are all created, hardwareContext.wireRobot() is called, and that is where all of the object graph is connected. This would allow me to create a new hardware context that wired the components differently if I needed to. It also allows me to create a new HardwareSpecification (the class that creates components), which creates different configurations for the robot.
This leads to a highly extensible and configurable system. It also makes it easy to figure out where to add a line of code. If the line is doing wiring, it goes into the Context, if it is creating an object, it belongs in the Spec. Easy, clean, clear.
Tags: decoupling, dependency injection, inversion of control, maintainability, Object Oriented Design, patterns, separation of concerns
