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.
respond | link |
/code
|