继承中的建造者模式

16
我想将Web服务URL请求表示为一个对象,并发现有许多常见参数可以在继承层次结构中“上升”。 请求可能有许多参数,一些是必需的,其他是可选的。对此,我认为Bloch的Builder模式是一个不错的选择,它使用流畅接口来模拟命名参数。
具体而言,我正在设计适用于Google Maps Web服务API的通用Web服务请求。
http://maps.googleapis.com/maps/api/service/output?{parameters}

serviceoutput是必填的参数,sensor是必填的参数。还有一个可选的参数language

每个服务都有一组必填和可选的参数。地理编码服务有两个可选参数boundsregion。它还具有互斥的必填参数addresslocation,它们分别指定了服务类型(直接或反向地理编码)。我使用新的子类表示此相互排斥。

我将类层次结构设想如下:

  .-----.
  | Url |
  '-----'
     ^
     |
.---------.
| Request |
'---------'
     ^
     |----------------------------+--------------...
.---------.                 .------------.
| Geocode |                 | Directions |
'---------'                 '------------'
     ^                            ^
     |------------+               .
 .--------.  .---------.          .
 | Direct |  | Reverse |          .
 '--------'  '---------'
然后,我想要做如下的事情:
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();

我该如何创建一个生成器(Builder),能够使用它所插入的类和超类中的参数和方法?

1个回答

19
我基于https://dev59.com/Umox5IYBdhLWcg3wi03F#9138629来构建我的答案,但考虑到这种多级层次结构。

我们需要使用Builder内部类来复制相同的层次结构。由于我们想要方法链接,我们需要一个getThis()方法来返回层次结构的叶子对象。为了向上传递其类型,父类具有通用的T,而叶子将T绑定到自身。

它确保类型安全并避免由于未初始化的必填参数或拼写错误而引发任何异常,并提供了漂亮的流畅接口。但是,将这样一个简单的结构表示为URL的设计非常昂贵和复杂。希望对某些人有用-我更喜欢在最后使用字符串连接。

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...
}

地理编码请求URL:

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...
}

直接地理编码请求URL:

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...
}

具体类中getThis()的重写实现不应该是抽象的。 - Eric Tobias
1
这太棒了!正是我在寻找的! - Maddy
这真的很有帮助,谢谢。尽管在我自己的代码版本中,似乎需要从继承树最顶层的抽象类访问通用方法(例如边界,区域)(我有3个级别)。 - Philippe
protected RequestUrl(Builder builder) method will always complain for Raw use of parameterized class 'Builder' - Ghilteras
@Ghilteras 您是正确的,我已经修复了中间抽象类的构造函数,使其也能够通过生成器类型进行参数化。 - Bruno Kim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接