Sunday, December 03, 2006

Jakarta Commons: Collections

Lately, I've been using the Jakarta Commons Collections library quite a bit since I've been back in Java 1.4 land. The library gives you everything you're used to with iteration methods available in Ruby and Smalltalk (do, collect, select, detect, etc). But, with the verbose and cumbersome syntax of anonymous methods the point of what you're trying to do gets lost. For example,
Collection activeAccounts=CollectionUtils.select(someAccounts, new Predicate() {
public boolean evaluate(Object object) {
return ((Account)each).isActive();
}
});
Wouldn't this be nicer:
Collection activeAccounts=CollectionUtils.select(someAccounts, Account.BY_ACTIVE);
One, it's lot less typing and reads much better in your logic. I've put the Predicate on the Account class. I really liked doing it this way and made my code read much more cleanly. Also, by having the Predicates defined on the objects that they were operating on, it made it easier to find which Predicates had already been defined. Everyone wins right? Well, no, I started using the same technique for Closures and Transformers too. My objects started to get crowded because I look putting my public static declarations at the top. Now, what to do?

Well, I was speaking to Matt Secoske one morning, we naturally started to discuss my dilemma of growing predicates and he had a simple answer: Put them in inner classes. At first, I scoffed at the idea, but after thinking about it, I came up with the following code:
public class Account {
public static final Predicates BY=new Predicates();

//Class stuff...

//Put this at the bottom of the class
static class Predicates {
public Predicate active() {
return new Predicate() {
public boolean evaluate(Object each) {
return ((Account)each).status.isActive();
}
};
}
}
}
I have no idea why I didn't like the idea at first. But now, my client code looks like this:
Collection activeAccounts=CollectionUtils.select(someAccounts, Account.BY.active());
The client reads the same, but now, I've gotten the implementation of the predicates out of the way. Everyone wins. I also put my transformers in their inner class named AS and one for closures called EACH. The important thing is that I keep meaningful information at the top and my client code reads well. Another thing to note is that now I can hide more information and expose less. You would be amazed at how much data is exposed in predicates. Yet, another nice side effect.

You will also notice that in the resulting code that I used a status instance variable to figure out if it was active so that I didn't have to duplicate the protocol of the status object through the Account object. My code has been getting simpler and it's been shocking me.

The point should always be to make the clients that use your objects life easier and more readable.

3 comments:

kerrin said...

(kerrin said... ha ha. Travis Griggs said it using kerrin's account :) )

"Wouldn't this be nicer:
Collection activeAccounts=CollectionUtils.select(someAccounts, Account.BY_ACTIVE);
"

What would be nice is:

someAccounts select: #isActive

Load SymbolValue from the VisualWorks Open Repository and that's all you need. Or you can load HigherOrderMessaging from the same repository and write:

someAccounts select isActive

[This message brought to by the Knights of Square Brackets, an order that masters syntax in the interest of solving problems]

Blaine said...

Yes, yes, I'm not going to argue it's nicer in Smalltalk or Ruby. We all know that. But, for those of us slogging it out in the trenches of verbosity, I thought this might help. Java is a great challenge to write code that reads well. They made it a little nicer in 1.5 with Iterable and the new for loop. But, in Smalltalk, it's so easy.

I wrote HOM as an exercise in Squeak named LazyCollections in SqueakMap. That project started out as a simple exercise in functional programming, but I realized how easy HOMs were to add to it. Smalltalk rules. By the way, I love the tag line at the bottom.

Jeff said...

I posted my take on it
here
.

Basically, static methods on the account clean up the client code and don't expose the CollectionUtils dependency:

Collection activeAccounts = Account.selectActive(someAccounts);

Amazon