Builder pattern with inheritance

陌路散爱 提交于 2019-12-03 05:09:08

问题


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?


回答1:


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 RequestUrl(Builder 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 GeocodeRequestUrl(Builder 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...
}


来源:https://stackoverflow.com/questions/10941464/builder-pattern-with-inheritance

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!