I've redesigned the way I envision @Buider to work once I add it to lombok based on this; it's an excellent idea as the boilerplate is generated anyway. My biggest concern with this design is that you blow up the ability to use a builder in the StringBuilder fashion - as an object that you pass along, where each place contributes its own little part of the full build of this object. Well, you still can, but it all has to be in a very specific order and you have to use specific type names. However, this style isn't as bad as the one we were discussing before; as a 'workaround', you can cast your SnpIdChromRiskOnly object back to the SnpDetail.Builder class to get access to everything. It's not the prettiest form, but you can do it, you can document that this will always work regardless of API revision, and splitting up the building process across a multitude of methods is generally rare.
It gets more complicated rather quickly when you start adding optionals into the mix; where do you store the default values? You can de-final your fields and just stick them on the field itself, but now you've got fields that are supposed to be immutable that aren't final, which feels a bit meh. Not to mention it could theoretically cause issues in a multi-threaded environment. Perhaps a private field named fieldName_DEFAULT is a better option, though it feels a bit hacky to it that way. To handle the optional nature of this, you'll need a specific 'build()' method so that the last required chain returns an interface with build() as well as set options for each optional field. Thanks for posting, Dick. Not something I've ever seen before, and really takes 'documentation-by-auto-complete' to the next level. --Reinier Zwitserloot On Nov 23, 4:03 pm, Dick Wall <[email protected]> wrote: > Hi, thanks for the interest. Here is the example builder I used in its > entirety: > > // The builder DSL > public interface SnpIdChromBothAllelesOnly { > public SnpDetail withOddsRatios(Map<Genotype, Double> > oddsRatios); > } > > public interface SnpIdChromRiskOnly { > public SnpIdChromBothAllelesOnly withNonRiskAllele(char > nonRisk); > } > > public interface SnpIdChromOnly { > public SnpIdChromRiskOnly withRiskAllele(char risk); > } > > public interface SnpIdOnly { > public SnpIdChromOnly withChromosome(String chrom); > } > > public static SnpIdOnly buildForId(String rsId) { > return (new Builder()).newFor(rsId); > } > > public static class Builder implements SnpIdOnly, SnpIdChromOnly, > SnpIdChromRiskOnly, SnpIdChromBothAllelesOnly { > > private String rsId; > private String chrom; > private char riskAllele; > private char nonRiskAllele; > > public SnpIdOnly newFor(String rsId) { > this.rsId = rsId; > return this; > } > > public SnpIdChromOnly withChromosome(String chrom) { > this.chrom = chrom; > return this; > } > > public SnpIdChromRiskOnly withRiskAllele(char risk) { > this.riskAllele = risk; > return this; > } > > public SnpIdChromBothAllelesOnly withNonRiskAllele(char > nonRisk) { > this.nonRiskAllele = nonRisk; > return this; > } > > public SnpDetail withOddsRatios(Map<Genotype, Double> > oddsRatios) { > return new SnpDetail(rsId, chrom, riskAllele, > nonRiskAllele, oddsRatios); > } > } > > It is, as mentioned previously, a wall of boilerplate (so much of Java > is), but this is still a neat pattern worth mentioning. In particular, > it uses different interfaces, each with a single method, returned from > each builder method, to lead you through the creation of the object. > This idea could be extended, for example if you needed either one > thing or another but not both, at some point you would return the > builder cast as an interface that offered only two methods, each of > which returning a different interface view of the builder, etc. > > The technique is overkill in this case, but if you have a DSL that is > intended to be used in a very controlled way and also is heavily used, > it starts to make more sense, since you can enforce object creation in > a way that you can only get the real object back once you have > satisfied all of the dependencies. > > The point of the talk was to mention that default and named parameters > largely eliminate the need for this kind of builder (although if you > really want to control everything about it, this same technique can be > used in Scala and other languages too). > > Cheers > > Dick > > On Nov 21, 12:33 pm, Jesper de Jong <[email protected]> wrote: > > > > > Thanks for the replies. Yes, Dick's builder pattern indeed had the > > fluent interface style, like FEST has. I'll have a look at how it's > > done in FEST. > > > Jesper > > > On 21 nov, 11:45, Moandji Ezana <[email protected]> wrote: > > > > On Sat, Nov 21, 2009 at 2:11 AM, Reinier Zwitserloot > > > <[email protected]>wrote: > > > > > This sounds like an interesting idea > > > > I've never seen before, and it also sounds like a massive wall of > > > > boilerplate. > > > > Entirely true. Again, check out > > > FEST-Reflect<http://fest.easytesting.org/reflect/> for > > > an example of how practical it is to use, and how impractical it is to > > > write. > > > > Moandji -- You received this message because you are subscribed to the Google Groups "The Java Posse" group. To post to this group, send email to [email protected]. To unsubscribe from this group, send email to [email protected]. For more options, visit this group at http://groups.google.com/group/javaposse?hl=.
