Brian Slesinsky's Weblog
 
   

Monday, 29 Sep 2003

Functional Style in Java, Part 2: Half-Beans

In my previous article, I gave a simple example of an immutable class called Album. You might have noticed that all of Album's properties have to be passed to the constructor, since there are no setter methods on an immutable class. That works okay for an class with four properties, but what about one with twenty? A programmer who uses your class and accidentally swaps the fifteenth and sixteenth String arguments in the constructor isn't going to like your design much. Some languages solve this problem with keyword arguments, but Java doesn't have them, so we need another solution. The Builder pattern comes to the rescue.

Here's an example of how to write a builder for Album. (The example is a little artificial; a builder isn't really necessary for such a simple class.)

public class AlbumBuilder {
    int albumID;
    String albumName;
    Artist artist;
    final List tracks;
 
    public AlbumBuilder() {
        tracks = new ArrayList();    
    }
 
    AlbumBuilder(Album a) {
        albumID = a.getAlbumID();
        albumName = a.getAlbumName();
        artist = a.getArtist();
        tracks = new ArrayList(a.getTracks());
    }
 
    // (setters omitted)
    ...
 
    public void addTrack(String trackName) {
        tracks.add(new Track(trackName));
    }
 
    public boolean isComplete() {
        return albumID >= 1 &&
               albumName != null &&
               artist != null &&
               tracks.size()>0;
    }
 
    public Album newAlbum() {
        return new Album(this);
    }
}
 
public class Album {
    private final int albumID;
    private final String albumName;
    private final Artist artist;
    private final List tracks;
 
    Album(AlbumBuilder builder) {
        if (!builder.isComplete()) {
            throw new IllegalArgumentException("AlbumBuilder is incomplete");
        }
        this.albumID = builder.albumID;
        this.albumName = builder.albumName;
        this.artist = builder.artist;
        this.tracks =
	    Collections.unmodifiableList(new ArrayList(builder.tracks));
    }
 
    // getters omitted
    ...
 
    public AlbumBuilder newAlbumBuilder() {
        return new AlbumBuilder(this);
    }
}

Here's an example of how AlbumBuilder would be used:

        AlbumBuilder b = new AlbumBuilder();
 
        b.setAlbumID(1);
        b.setAlbumName("Hello");
        b.setArtist(new Artist("Someone"));
	b.addTrack("Happy Birthday");
 
        Album a = b.newAlbum();

I'm calling this a Half-Bean pattern because you take an ordinary JavaBean and cut it in half. The getters go into one class and the no-arg constructor and setters go in the other. If it looks familar, you're right - it's basically the same pattern as String/StringBuffer.

Doing things this way might not look any simpler. There is one more method call than in constructing an ordinary JavaBean and twice as many classes. It's also slightly non-standard and might not work well with frameworks that expect standard JavaBeans. But the advantage is that half-beans clearly distinguish between bean construction and bean usage, so it's much easier to enforce constraints, especially when they're on more than one property. And the clients of an immutable bean gain the usual benefit of immutable objects: easy sharing with no synchronization worries.

A couple of subtle points about the design:

First, notice that neither class inherits from the other, and there is no common interface. This is intentional; if a method expects an immutable object, you shouldn't be able to pass it an object that might change under it. There is certainly a close relationship between an Album and an AlbumBuilder, but they can't substitute for each other.

(That's why I don't recommend following the example of the unmodifiable collection wrappers in java.util. They use wrappers to implement immutability, which can easily be subverted. You can only trust unmodifiable collections that you've created yourself. The java.util approach makes sense for wrapping large collections where copying would be expensive, but it's not as simple to use as the String/StringBuffer approach.)

In practice, the lack of a common interface is no great hardship. If you have an Album and need an AlbumBuilder, or vice versa, you can use the AlbumBuilder.newAlbum() and Album.newAlbumBuilder() to convert between them (at the expense of a copy).

Second, notice that the copying constructors are non-public. That makes it easy to subclass Album and AlbumBuilder and arrange for the subclasses to convert to each other without slicing off subclass properties.

It might seem odd to spend so much time on constructors. However that's typical of functional programming style - most of the work is in constructing the right object. Once you have the object you want, you're done.