I had read something a while ago about a design pattern called Inversion of Control (IoC), which came back to me this morning as I was working. I looked it up on Google and found a decent overview of the pattern, and wanted to share it: IoC Article
The basic jist of IoC is that if your class needs an object that is
outside of its primary responsibilities, it should not instantiate it
itself, but rather should accept that object either in a constructor or
with a setter method. This writeup uses a
database object manager for it’s example, however a more common example
is logging. I’d be willing to bet that at the top of most Java Code
with logging, you’d see a line that looks like this:private final static Logger LOG = Logger.getLogger( myclass.class );
And then the rest of the methods in that class use the LOG instance. IoC says that it is NOT your class’ responsibility to instantiate a logger, and therefor should be passed one instead.
Some benefits to doing this:
- It ties in well with Test-First programming, as it helps to minimize the amount of setup needed in your testcases. In the case of logging, it means that you can pass in a “dummy” Log implementation rather than configuring a Log4J runtime.
- It allows us to log based on “use-cases” rather than at a functional level. Take for instance the core/Reports framework. Every class in core/Reports right now has it’s own logger, but in general, I only use 2 or 3 of the classes in the package (t he classes which expose the high-level functionality of the framework). If I implemented IoC with respect to Logging in core/Reports, I’d probably only have 2 distinct Log instances in the entire package, making it a) easy to configure logging for core/re ports and b) easy to identify flow-of-control in the logging output.
- It minimizes the amount of work we need to do when we all of a sudden decide we want to, say, drop in commons-logging rather than use Log4J directly :).
IoC happens to be very easy to re-factor in, here’s an example of how that would be done without breaking compatibility:
Existing Class:
public Class Foo {
private final static Logger LOG =
Logger.getLogger( Foo.class );
public Foo() {
// Initialize the class here.
}
...
}
Refactored class:
public Class Foo { private Logger LOG = null; public Foo() { // Default constructor creates a logger so // as to not break any existing code. this( Logger.getLogger( Foo.class ) ); } /** * New usages of Foo should use this class * instead, passing in a Logger instance. */ public Foo( Logger log ) { // Initialize the class here. this.LOG = log; } public void setLogger( Logger log ) { this.LOG = log; } }
You should really take the time at this point to find usages of the “old” constructor, update them to usages of the new constructor, and remove the old constructor. This may not be very easy to accomplish, especially when you’re refactoring libraries.. . The refactoring shown above guarantees that no current usages of your code will break. New code, however, should implement IoC and NOT give users the option of using a Default logger, as follows:
New Class:
public Class NewFoo { private Logger LOG = null; // Explicitly remove the ability of constructing // this class w/o a logger. This prevents outside // instantiations, and subclasses of this class // from doing this. private NewFoo() { } public NewFoo( Logger log ) { this.LOG = log; } }