Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Builder pattern with inheritance

I want to represent an web service URL request as an object, and found that there are lots of common parameters that could be "bubbled up" in an inheritance hierarchy. A request could have lots of parameters, some mandatory and other optional, for which I believe Bloch's Builder pattern is a nice option, emulating named arguments with a fluent interface.

Specifically, I'm designing for the Google Maps web service API, that has as general web service request

http://maps.googleapis.com/maps/api/service/output?{parameters}

service and output are mandatory arguments, and sensor a mandatory parameter. There is also an optional parameter language.

Each service has its set of mandatory and optional parameters.The Geocode service has two optional parameters, bounds and region. It also has mutually exclusive mandatory parameters, address or location, that specify the type of service (direct or reverse geocoding, respectively). I represent this mutual exclusion with new children classes.

I imagine the class hierarchy as such:

  .-----.
  | Url |
  '-----'
     ^
     |
.---------.
| Request |
'---------'
     ^
     |----------------------------+--------------...
.---------.                 .------------.
| Geocode |                 | Directions |
'---------'                 '------------'
     ^                            ^
     |------------+               .
 .--------.  .---------.          .
 | Direct |  | Reverse |          .
 '--------'  '---------'

Then, I would like to do something like the following:

String output = "xml";
boolean sensor = true;
String address = "Av. Paulista, São Paulo, Brasil";
Bounds bounds  = new Bounds(-20, -10, -25, -20); //Geographic rectangle
String region  = "br";
String lang    = "pt-BR";
Coord location = new Coord(-12,-22);

DirectGeocodeRequestUrl direct = 
    new DirectGeocodeRequestUrl.Builder(output, sensor, address)
                               .bounds(bounds)
                               .language(lang)
                               .build();

ReverseGeocodeRequestUrl reverse = 
    new ReverseGeocodeRequestUrl.Builder(output, sensor, location)
                                .language(lang)
                                .region(region)
                                .build();

How can I create a Builder that uses arguments and methods from the class and superclasses in which it is inserted?

like image 665
Bruno Kim Avatar asked Jun 07 '12 23:06

Bruno Kim


People also ask

How do builders use inheritance patterns?

Make a generic builder for each class that will have a subclass builder. This builder will already contain the setter methods for the current class, but we create also a second non generic builder for the class that contains the constructor and build method. The builders will not have any fields.

What is difference between @builder and @SuperBuilder?

The @SuperBuilder annotation produces complex builder APIs for your classes. In contrast to @Builder , @SuperBuilder also works with fields from superclasses. However, it only works for types. Most importantly, it requires that all superclasses also have the @SuperBuilder annotation.

Is Lombok Builder Pattern?

Project Lombok's @Builder is a helpful mechanism for using the Builder pattern without writing boilerplate code. We can apply this annotation to a Class or a method. In this quick tutorial, we'll look at the different use cases for @Builder.

Is Builder pattern an anti pattern?

When Builder Is an Antipattern. Unfortunately, many developers pick only part of the Builder pattern — the ability to set fields individually. The second part — presence of reasonable defaults for remaining fields — is often ignored. As a consequence, it's quite easy to get incomplete (partially initialized) POJO.


1 Answers

I'm building my answer upon https://stackoverflow.com/a/9138629/946814, but considering this multi-level hierarchy.

What we need is to replicate the same hierarchy with the Builder inner classes. As we want method chaining, we need a getThis() method that returns the leaf object of the hierarchy. In order to pass its type upward the hierarchy, the parent classes have a generic T, and the leaf binds T to itself.

It assures type-safety and avoids any exception throwing due to uninitialized mandatory parameters or typos, plus the nice fluent interface. However, it's a very costy and complex design to represent such a simple structure as an URL. I hope it is useful to someone - I preferred string concatenation at the end.

RequestUrl:

public abstract class RequestUrl{
    public static abstract class Builder<T extends Builder<T>>{
        protected String output;
        protected boolean sensor;
        //Optional parameters can have default values
        protected String lang = "en"; 

        public Builder(String output, boolean sensor){
            this.output = output;
            this.sensor = sensor;
        }

        public T lang(String lang){
            this.lang = lang;
            return getThis();
        }

        public abstract T getThis();
    }

    final private String output;
    final private boolean sensor;
    final private String lang;

    protected <T extends Builder<T>> RequestUrl(Builder<T> builder){
        this.output = builder.output;
        this.sensor = builder.sensor;
        this.lang = builder.lang;
    }

    // other logic...
}

GeocodeRequestUrl:

public abstract class GeocodeRequestUrl extends RequestUrl {
    public static abstract class Builder<T extends Builder<T>>
        extends RequestUrl.Builder<Builder<T>>{

        protected Bounds bounds;
        protected String region = "us";

        public Builder(String output, boolean sensor){
            super( output, sensor );
        }

        public T bounds(Bounds bounds){
            this.bounds = bounds;
            return getThis();
        }

        public T region(String region){
            this.region = region;
            return getThis();
        }

        @Override
        public abstract T getThis();
    }

    final private Bounds bounds;
    final private String region;

    protected <T extends Builder<T>> GeocodeRequestUrl(Builder<T> builder){
        super (builder);
        this.bounds = builder.bounds;
        this.region = builder.region;
    }

    // other logic...
}

DirectGeocodeRequestUrl:

public class DirectGeocodeRequestUrl extends GeocodeRequestUrl {
    public static class Builder<Builder>
        extends GeocodeRequestUrl.Builder<Builder>{

        protected String address;

        public Builder(String output, boolean sensor, String address){
            super( output, sensor );
            this.address = address;
        }

        @Override
        public Builder getThis(){
            return this;
        }

        public DirectGeocodeRequestUrl build(){
            return new DirectGeocodeRequestUrl(this);
        }
    }

    final private String address;

    protected DirectGeocodeRequestUrl(Builder builder){
        super (builder);
        this.address = builder.address;
    }

    // other logic...
}

ReverseGeocodeRequestUrl:

public class ReverseGeocodeRequestUrl extends GeocodeRequestUrl {
    public static class Builder<Builder>
        extends GeocodeRequestUrl.Builder<Builder>{

        protected Coord location;

        public Builder(String output, boolean sensor, Coord location){
            super( output, sensor );
            this.location = location;
        }

        @Override
        public Builder getThis(){
            return this;
        }

        public ReverseGeocodeRequestUrl build(){
            return new ReverseGeocodeRequestUrl(this);
        }
    }

    final private Coord location;

    protected ReverseGeocodeRequestUrl(Builder builder){
        super (builder);
        this.location = builder.location;
    }

    // other logic...
}
like image 182
Bruno Kim Avatar answered Oct 13 '22 07:10

Bruno Kim