[]
What a good container can do for you
[
jdeolive
]
01:54, Friday, 11 November 2005
Some Background
I am sure you have heard the term container used over and over. Probably in a J2EE environment where they originated (or where they became popular). An informal definition of a container could be "An environment which hosts objects inside of it, providing the objects with various services".
A service often provided by a container is Life Cycle Management. This provides hosted objects with events or hooks that are executed at certain stages during the life of an object such as creation or destruction. These hooks allow an object to initialize and clean up after itself in a controlled manner.
A container often manages the dependencies among the objects inside of it. This is nice beacuse an object does not have to go looking around for another object it depends on, it can simply ask the container for it. If you program with J2EE and Enterprise Java Beans, this amounts to something like a JNDI lookup.
A spin on dependency management is another buzz word I am sure you have heard at some point, Inversion of Control (IoC). This essentially turns the problem inside out by having the container automagically provide an object with its dependencies, instead of having the object look them up.
Closley related to the concept of IoC is another known as Dependency Injection. Injection amounts to having an object being provided or "injected" with its dependencies. It has two flavors known as Constructor Injection and Setter Injection.
This introduction does not do justice to the conecpts of containers and injection. Please read the great article by Martin Fowler Inversion of Control Containers and the Dependency Injection pattern
Benefits
Now what does all this buy you in the end?
Simplicity
Designing to use injection is a very simple and natural way to create classes. It does not require any facilities beyond the Java language itself, and leaves your code understandable. As the KISS mantra states "Keep it simple stupid".
Portablilty
Injected classed often do not have any dependence on a particular container so they can be easily used in a different container without any modifications. You can still write POJO's and have them work in the container of your choosing.
This is of critical importance to the framework programmer. Those of you that develop frameworks and toolkits have no idea about what kind of environment people will want to run your code in. Users often have their own container or application server they must use. Designing using the principals of injection can leave users options open.
It is also of importance to the application programmer as they need to know if they can use a certain framework or toolkit with their companys specific application server.
Assembly
Injection removes the desicion about what specific instance of a class to use from the class that depends on it. Instead it is up to whomever is assembling the container to decide. Consider the following example:
class Foo {
FooPart[] parts;
public Foo(FooParts[] parts) {
this.parts = parts;
}
}
class FooBuilder {
FooFactory factory;
FooBuilder() {
factory = new DefaultFooFactory();
}
Foo buildFoo() {
FooPart[] parts = new FooPart[2];
parts[0] = factory.createFooPart1();
parts[1] = factory.createFooPart2();
return factory.createFoo(parts);
}
}
interface FooFactory {
FooPart createFooPart1();
FooPart createFooPart2();
Foo createFoo(FooPart[] parts);
}
See the problem? What happens if a user comes along and asks "How do I use my own specific implementation of FooFactory?". This question can come up for a number of reasons.
- The user needs to create a subclass of Foo
- The user may need to use an implementation of FooFactory specific to their environment
As the code is written this cannot be acheived without a change, which in many cases is unacceptable. The problem is that a framework class (FooBulder) is making a decision about the specific implementation of FooFactory to use. A better solution would be to have FooBuilder injected with a FooFactory (in this case, one provided by the assembler).
class FooBuilder {
FooFactory factory;
FooBuilder(FooFactory factory) {
this.factory = factory;
}
Foo buildFoo() {
FooPart[] parts = new FooPart[2];
parts[0] = factory.createFooPart1();
parts[1] = factory.createFooPart2();
return factory.createFoo(parts);
}
}
This change leaves it in the hands of the assembler which implementation to use. Giving the users this flexibility improves the usablity and flexibilty of your codebase.
So the moral here is leave assembly to the entity that is using yoru codebase, dont make any decisions for them. The assembly could look something like this:
Container container = new Container();
container.register(MyFooFactory.class);
container.register(FooBuilder.class);
FooBuilder builder = container.get(FooBuilder.class);
Lightweight
It is usually not viable for a developer to transition his or her codebase over to a new technology, even if it has serious technological benefits. Good luck convincing management it is worth it. However a viable alternative is the addition of a lightweight container to the codebase, and a slow transition over to using it.
There are many lightweight container implementations available today. PicoContianer is a popular one. Spring is a good choice if you are doing web application development. The key with a lightweight container is that it can be embedded in virtually any other environment.
Conclusion
So the moral of the story is that containers and injection can make your life and the lives of your users easy. And with the arrival of several lightweight containers you dont have to be a J2EE programmer to enjoy the benefits of doing so.