Inversion of Control
Listen to this article
"I was expecting a paradigm shift and all I got was a lousy constructor!"
Perryn Fowler has asked me how would we use the Clock? How do we create one for production? The Inversion of Control (IoC) fraternity frown upon factories so what are we to do?
Luckily, software developed using TDD tends to lend itself to IoC.
This example conforms just fine with so called "Type-3" IoC which is based on passing dependencies via constructors. It's a very simplistic example nonetheless. For more complex systems, you may want to check out IoC containers such as PicoContainer et. al. which seem to be gaining in popularity. And for good reason.
These containers allow you to easily configure components and the dependencies between them. Although I must admit that I'm not a fan of the hype as some people have been building systems like this for a while now. The up-side of course is that others will hopefully feel more comfortable looking into it as an alternative now that it has a snazzy name and the obligatory TLA :-)
Once you get your head around it, it's pretty easy to see why people love the concept of IoC containers so much. In some ways it's not that much different to plugins but it does lend itself to other cool stuff, like decorating and "passivating". No more JNDI lookups, no more creating endless factories with staic methods. But as I said it can be hard to see at first how that works.
So here is the simplest example I could think of and whip up in 20mins. It displays a panel into which you can enter a duration to wait (in milliseconds) and a message to display once the time has elapsed.
It hopefully demonstrates how we can now use Alarms knowing they are fully tested and how we don't need a static factory to create anything.
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Main extends JFrame {
private final JTextField _durationMillisField = new JTextField("3000");
private final JTextField _messageField = new JTextField("Message!");
private final JButton _setButton = new JButton("Set");
private final Clock _clock;
public Main(Clock clock) throws HeadlessException {
if (clock == null) {
throw new IllegalArgumentException("clock can't be null");
}
_clock = clock;
getContentPane().setLayout(new GridLayout());
getContentPane().add(_durationMillisField);
getContentPane().add(_messageField);
getContentPane().add(_setButton);
_setButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
setClicked();
}
});
setSize(300, 50);
}
private void setClicked() {
int durationMillis = new Integer(_durationMillisField.getText()).intValue();
Popup popup = new Popup(_messageField.getText());
Alarm alarm = new Alarm(popup, _clock.getCurrentTimeMillis() + durationMillis, _clock);
new Thread(alarm).start();
}
private class Popup implements Runnable {
private final String _message;
public Popup(String message) {
if (message == null) {
throw new IllegalArgumentException("message can't be null");
}
_message = message;
}
public void run() {
JOptionPane.showMessageDialog(Main.this, _message);
}
}
public static void main(String[] args) {
new Main(new SystemClock()).show();
}
}
Nothing too complex here. It doesn't go into testing GUIs (a topic for a book perhaps?) but as you can see, it would be possible to break down even this example into smaller chunks for testing. But really, the only thing the JFrame does is convert from screen values to primitives in order to create Alarms.
(As an aside, my original example had a Scheduler that created Alarms and setthem running in a Thread but again I thought I'd ke it as simple as I could so I simply put the code into Main.)
In some ways it seems almost trivial really. But if you can imagine a whole system done this way it's a pretty cool way to build apps. In a bizzarre way, it's kind of like rule based systems such as CLIPS or JESS only intead of delcarative rules that magically get evaluated, in this case, the dependencies are declarative and are magically resolved at run time.
Take a look at the other examples on the net and drop me a line if you want me to put together a more complex example using say PicoConatiner or NanoContainer.
Comments
What is the big deal? Isnt that logical?
God, I have been hearing a lot about these IOC principles - cryptic, no easy documentation. I finally decide to read up on it and its just normal programming? Jees!
Seems really weird there is so much publicity over something that is essentially basic design!
Posted by: ben | December 15, 2003 03:42 AM
Spot on Ben. That's why PicoContainer's slogan is:
http://tinyurl.com/wj7p (Use it as a wallpaper if you dare).
Yes, it's just basic good design. Basic good design that people have been failing to use for ages. With the popularisation of IoC, more people are going to be aware of this way to design highly decoupled systems, and that is good.
Example: Take a look at e.g. WebWork1 or Struts actions. They have to have empty constructors and the container has no support for IoC (not even setter based IoC). This means that the services used by the actions (like persistence layer) often are instantiated by the action itself, in a hard-coded way. That is a real pain when it comes to testing the Actions.
So the big deal here is to get people to design frameworks and components around the principles of IoC. Most people don't. Yet.
And IoC containers are needed when the components are defined in a dynamic way. IoC containers are responsible for:
a) Instantiating objects.
b) Lacing them together.
If you have a small number of components in your system, and they never change, you don't need an IoC container. You can instantiate and lace everything "by hand" in your Java code.
However, when the number of components increases, get defined dynamically or the complexity of the dependencies between them increases, the need for an IoC container can really be justified.
Posted by: Aslak Hellesoy | December 15, 2003 04:27 AM
Ben, I couldn't agree more, hence "Although I must admit that I'm not a fan of the hype..." I too have been doing this for some time. Ever since I got on the TDD bandwagon. I guess I wanted to de-mystify the whole thing without dismissing it. As Aslak points out, for simple stuff the good-old hand code stuff is just fine. I hand-code complex stuff as well. And as I was dicussing with Damian Guy, for people like you and I, this stuff all seems rather trivial. But then 10-dimensional spaces seemed trivial to Richard Feynman :-)
Posted by: Simon Harris | December 15, 2003 06:28 AM
Or was that International Olympic Committee? But anyway...
Posted by: Ross Bradley | December 23, 2003 04:08 PM
IOC is Inverse of Control ;)
Posted by: magician | December 30, 2003 01:38 AM
I was about to rant on about IoC being an obvious by-product of factoring (assigning responsibilities) and cross-cutting (aka AOP). But, just in the nick of time, I realised that was rubbish. i.e. IoC removes the responsibility for *creation* from the client class as well as the responsibility for implementation. In other words, not only is your GUI class *not* an Alarm, it also is not responsible for finding one.
You might wonder where I was getting the cross-cutting confusion from? Well. My thoughts went something like this: Main is not an Alarm, it *uses* and Alarm. The most pernicious misassignment of roles of this type is to embed persistence into a class. Cross-cutting is one way in which such aspects of a class can be factored out.
I almost wish I hadn't started this now :blush:.
Posted by: Paul Andrews | January 21, 2004 02:29 PM