Brian Slesinsky's Weblog
 
   

Friday, 03 Oct 2003

Struct Arguments: a Lighter Alternative to Half-Beans

After thinking about it a while, I've decided that main thing wrong with my Half-Bean design pattern is that it's over-engineered. I had some additional considerations in mind (such as validating web forms) that most classes are Not Going To Need.

Some context: the problem I'm trying to solve is initializing an immutable object with a large number of properties (say, 20). The solution should allow instance variables to be declared final, and support arbitrary constraints enforced at construction time.

After stripping my album example down to its bare essentials, it looks like this:

public class Album {
 
    public static class Args {
        public int albumID;
        public String albumName;
        public Artist artist;
        public List tracks = new ArrayList();
    }
 
    private final int albumID;
    private final String albumName;
    private final Artist artist;
    private final List tracks;
 
    public Album(Args args) {
        this.albumID = args.albumID;
        this.albumName = args.albumName;
        this.artist = args.artist;
        this.tracks = Collections.unmodifiableList(new ArrayList(args.tracks));
        if (!isComplete()) {
            throw new IllegalArgumentException("incomplete arguments");
        }
    }
 
    protected boolean isComplete() {
        return albumID >= 1 &&
               albumName != null &&
               artist != null &&
               tracks.size()>0;
    }
 
    // example usage
    public static Album newFromProps(Properties props, String prefix) {
        Args args = new Args();
 
        args.albumID = Integer.parseInt(props.getProperty(prefix+".id"));
        args.albumName = props.getProperty(prefix+".name");
        args.artist = new Artist(props.getProperty(prefix+".artist"));
 
        for (int trackNum=1; ; trackNum++) {
            String trackProp = props.getProperty(prefix+".track"+trackNum);
            if(trackProp==null) break;
            args.tracks.add(new Track(trackProp));
        }
 
        return new Album( args );
    }
 
    // getters omitted
}

Instead of a full-fledged class like AlbumBulder, I used the equivalent of a C struct to pass arguments to the constructor. This technique has long been used in C for calling functions with large numbers of parameters. I'll call it the Struct Argument pattern unless someone comes up with something better.

One nice side-effect is that when you add a new property, you have the choice of making it required or optional, depending on whether you add a null check to the isComplete() method. Adding an optional parameter doesn't require any change to the code calling Album(). Unfortunately, adding a required parameter won't cause a compiler error, but a runtime error in the Album constructor is the next best thing.

I expect that most code that uses an Album wouldn't call the constructor directly, but rather a factory method with a more specialized interface such as Album.newFromProps(). There might be factory methods for creating albums from a ResultSet, an XML Element, or an HttpRequest. Making the Album(Album.Args) constructor public allows such factory methods to be written outside of Album's package, for example if you want to keep the persistence layer separate. (An alternative design for internal API's would be to force all factory methods to be added to be kept together in Album by making the constructor protected.)

It might turn out that over time, the Args inner class evolves from a struct to a full-fledged class like AlbumBuilder, but that should be based on having a real need for it.