Programmquelltext

Java: DSL-like Builders with interfaces instead of classes


I am deve­lo­ping in Java now for some 27 years, the most part of it for a living. The­re are many cle­ver things I’ve seen in this time writ­ten and inven­ted by others. So, I was real­ly sur­pri­sed when I had an idea today I can­not remem­ber I have ever seen befo­re. I wro­te a first imple­men­ta­ti­on and it seems to work. And as it a qui­te basic struc­tu­re I ask mys­elf: Has no-one ever ever done this so far?

It’s about the fol­lowing thing: A qui­te com­mon pat­tern for buil­ders is the DSL-like approach of step-wise sub­clas­ses. I lear­ned this while fiddling around with JOOq and imple­men­ted it mys­elf several times somehow like this:

class SomeBuilder {

  int field1;
  String field2;

  public Step2 withField1(int field1) { this.field1=field1; return new Step2(); }

  public class Step2 {
    public BuildStep withField2(String field2) { this.field2=field2; return new BuildStep(); }
  }

  public class BuildStep {
    public SomeThing build() { return new SomeThing(field1,field2); }
  }

}

...

SomeThing someThing=new SomeBuilder().withField1(15).withField2(''hello'').build();

This is a known pat­tern I like to use for more com­plex buil­ders when it is important not to for­get fiel­ds in the buil­ding pro­cess. Dis­ad­van­ta­ge: Lots of class instan­ces are crea­ted during the buil­ding pro­cess. And it loo­ks rather scat­te­red as the busi­ness logic is hid­den in all that sub­clas­ses. A „nor­mal” buil­der just returns its­elf again and again so that not­hing has to be crea­ted. And all methods are at the same level next to each other. But a „nor­mal” buil­der can­not gui­de you through the buil­ding pro­cess – you always have access to all build methods.

Or – can it?

What if we split the API of the buil­der into chunks con­tai­ning only some of the methods and gui­ding the cal­ler through the pro­cess just as the class-based approach abo­ve. Java has a lan­guage fea­ture for this: interfaces:

class SomeBuilder {

  public interface Step1 {
    Step2 withField1(int field1);
  }

  public interface Step2 {
    BuildStep withField2(String field2);
  }

  public interface BuildStep {
    SomeThing build();
  }

  public static class Process implements Step1,Step2,BuildStep {

    int field1;
    String field2;

    public Step2 withField1(int field1) { this.field1=field1; return this; }
    public BuildStep withField2(String field2) { this.field2=field2; return this; }

    public SomeThing build() { return new SomeThing(field1,field2); }
  }

  public static Step1 create() { return new Process(); }

}

...

SomeThing someThing=SomeBuilder.create().withField1(15).withField2(''hello'').build();

This does exact­ly the same thing and allows exact­ly the same gui­d­ance as the class-based approach abo­ve. Ok, you have to wri­te „SomeBuilder.create()” ins­tead of „new Some­Buil­der()”, but that’s real­ly not a dif­fe­rence – and if the buil­der is gene­ra­ted by a sta­tic „SomeThing.builder()” method, the­re is no dif­fe­rence in cal­ling at all.

This approach also seems to have the advan­ta­ge that it’s real­ly easy to model optio­nal fiel­ds. If „field2” can be left out, it’s simp­le to model that: You just add ano­t­her interface

  public interface Step2OrBuildStep extends Step2,BuildStep {}

… and let Step1::withField1 (and Process::withField1) return this interface:

  public interface Step1 {
    Step2OrBuildStep withField1();
  }

...

  public Step2OrBuildStep withField1(int field1) { this.field1=field1; return this; }

The real­ly nice thing as I see it is that buil­ding pro­cess and build seman­ti­cs are total­ly sepa­ra­ted from each other: The buil­der can be writ­ten as usu­al. Only the inter­faces crea­te the gui­d­ance through the methods. Yes, you have the dupli­ca­ti­on of the buil­der methods in the inter­faces – but I don’t think this to be real­ly bad. After all, the IDE can to most of the work („crea­te mis­sing methods”) if the inter­faces are writ­ten first and the buil­der pro­cess class after­wards. And the­re is only one class instance gene­ra­ted through the buil­ding pro­cess, not a num­ber of tiny subclasses.

Is this real­ly a cle­ver idea or do I over­see something?

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.