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