Usage of abstract class in the builder pattern?

2019-09-06 02:42发布

问题:

I have two types of payload coming from upstream: It's either PayloadA or PayloadB. There are some common fields between PayloadA and PayloadB so I created Payload class with those common fields and for rest I created two builder class one for each payload.

Below is the builder class for PayloadA:

public final class PayloadA {
  private final String clientId;
  private final String langid;
  private final String deviceId;
  private final Map<String, String> applicationPayload;

  private PayloadA(Builder builder) {
    this.clientId = builder.clientId;
    this.langid = builder.langid;
    this.deviceId = builder.deviceId;
    this.applicationPayload = builder.applicationPayload.build();
  }

  public static class Builder {
    protected final String deviceId;
    protected String clientId;
    protected String langid;
    protected ImmutableMap.Builder<String, String> applicationPayload = ImmutableMap.builder();

    public Builder(String deviceId) {
      this.deviceId = deviceId;
    }

    public Builder setClientId(String clientId) {
      this.clientId = clientId;
      return this;
    }

    public Builder setLangid(String langid) {
      this.langid = langid;
      return this;
    }

    public Builder setPayload(Map<String, String> payload) {
      this.applicationPayload.putAll(payload);
      return this;
    }

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

    // getters and to string here
}

Now below is the class for PayloadB:

public final class PayloadB {
  private final String clientid;
  private final String type;
  private final String payId;

  private PayloadB(Builder builder) {
    this.clientid = builder.clientid;
    this.type = builder.type;
    this.payId = builder.payId;
  }

  public static class Builder {
    protected final String type;
    protected String payId;
    protected String clientid;

    public Builder(String type) {
      this.type = type;
    }

    public Builder setPayId(String payId) {
      this.payId = payId;
      return this;
    }

    public Builder setClientId(String clientid) {
      this.clientid = clientid;
      return this;
    }

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

    // getters and to string here
}

Now I have created another class which is Payload class (does this have to be abstract class?) in which I have all the common fields both for PayloadA and PayloadB so I have to set these fields as well somehow and I am not sure how to use below class:

public abstract class Payload {
  private long createTimestamp;
  private String partition;
  private String key;
  // some other fields here

  // getters and setters here
}

Question:

  • Now let's say if we get PayloadB from upstream, then I want key field in the Payload class to be whatever is the value of type in PayloadB class in all lower case and if we get PayloadA from upstream, then I want key to be world.
  • And also if we get PayloadB from upstream and if clientId was set, then I want partition to be 15 and if we get PayloadA from upstream and if clientId was set then I want partition to be 15 but if it was not set and langId was there, then I want partition to be 17.
  • And I want to set createTimestamp as well which I have to do after building a Payload object. So for example I have build PayloadA object and it will be passed to some other class and there I need to set createTimestamp value on PayloadA object. Not sure how to do that as well? Do I have to clone something?

How can I use Payload class in my builder pattern? I will get two different payloads and there will be few things common in them so common fields I have separated them out in an abstract class.

Should I have on big builder pattern class with everything in it or multiple builder pattern extending something?

回答1:

  • I won't pass builder instance to the PayloadX constructor. Either pass values as individual constructor arguments or call setters.
  • You can define Payload.Builder which would hold common fields of PayloadA and PayloadB. This class will be an abstract class declaring an abstract build method.
  • PayloadA.Builder and PayloadB.Builder will extend Payload.Builder, implementing the build method.
  • In this build method you implement whatever custom logic you need to create and set the fields of the PayloadX.

It seems like you want to make your class immutable (careful with applicationPayload by the way). In this case you can't really "set" anything. You can only produce a new instance. There are many ways to do this, for instance you can implement PayloadX withTimestamp(...) method. Or you can extend your build to accept PayloadX and set timestamp there, resulting in something like new PayloadX.Builder(payloadXInstance).setTimestamp(...).build().