john
john

Reputation: 11669

Subclassing abstract class in a builder pattern?

I have two types of payload coming from upstream: It's either PayloadA or PayloadB. PayloadA has lot of fields in it as compared to PayloadB but there are some common fields between PayloadA and PayloadB. Just to make example simpler, I have added only few fields.

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;
  // other fields as well

  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;
  // other fields as well

  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 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 key;
  // some other fields are here

  // getters and setters here
}

Question:

Now from the below code I am making either PayloadA or PayloadB depending on what is passed.

  private void run(String name) {
    // .. some code here
    if (name.equalsIgnoreCase("PayloadA")) {
      Payload payload =
          new PayloadA.Builder(getDeviceId()).setClientId("someid").setLangid("anotherid")
              .setPayload("some map").build();
      DataProcessor.getInstance().process(payload);
    } else {
      Payload payload =
          new PayloadB.Builder(getType()).setPayId("someid").setClientId("anotherid").build();
      DataProcessor.getInstance().process(payload);
    }
  }

And in the DataProcessor process method:

  private void process(Payload payload) {
    // 1) here I need to set createTimestamp and key variables on payload bcoz they are common
    // fields.
    // 2) Also how can I figure out whether payload is PayloadA or PayloadB here?
  }

Now how can I set createTimestamp and key variable which is in Payload class in the process method? Right now I have a run method where I am differentiating it but in general I will have a different upstream code for PayloadA and different upstream code for PayloadB so depending on that we will use either of the Payload class.

Also should I have two different builders here or one big builder here doing everything?

Upvotes: 0

Views: 1016

Answers (3)

fabfas
fabfas

Reputation: 2228

public class Payload {
 // common fields
 private String clientid;
 private String key;
 private long Timestamp;

 // setter and getters 
}

public class PayloadA extends Payload implements PayloadStrategy {
 // PayloadA specific fields
 private String langid;
 private String deviceId;

 // setters and getters

 public static class Builder {
 // existing code
 } 

 @Override
 public void process() {
    System.out.println("PayloadA specific implementation");
 }

}

public class PayloadB extends Payload implements PayloadStrategy {
 // PayloadA specific fields
 private String type;
 private String payId;

 // setters and getters

 public static class Builder {
 // existing code
 }

 @Override
 public void process() {
    System.out.println("PayloadB specific implementation");
 }
}

}

// define contract
public interface PayloadStrategy {
 public void process();
}

// payload context
public class PayloadContext {

 PayloadStrategy strategy;

 public void setContext(PayloadStrategy payloadStrategy) {
    this.strategy = payloadStrategy;
 }

 public void processPayload() {
    strategy.process();
 }
}

// parameterized (PayloadA or PayloadB) payload run method 

PayloadContext context = new PayloadContext();
context.setContext(new PayloadA());
context.processPayload();

Upvotes: 0

Roberto Attias
Roberto Attias

Reputation: 1903

abstract public class Payload {
  abstract void setKey();
}

public class PayloadA extends Payload  {
    //add existing code
   void setKey() {
      key = "a";
   }
}
public class PayloadB extends Payload {
    //add existing code
  void setKey() {
    key = "b";
  }
}

private void process(Payload payload) {

     //Depending upon the object passed, fields will be set for A or B       

     payload.setCreateTimestamp(ADD_DATA1);
     payload.setKey();
}

Edit:

The idea here is that any class extending payload must either implement the setKey() method, or be abstract itself. so, PayloadA and PayloadB both implement the method. Each class provides a different implementation.

Now suppose you do

PayloadA pa = new PayloadA();
pa.setKey()

As expected, the implementation actually executed would be the one defined in PayloadA.

Now consider this case:

Payload pa = new PayloadA();
pa.setKey()

Despite the variable being declared of type Payload, the actual type of the object referred to by the variable is PayloadA, hence the setKey() call is the one in PayloadA. This is referred to as Dynamic Dispatching, because which implementation is called is known at run-time, not at compile time

Upvotes: 1

Vasu
Vasu

Reputation: 22402

PayloadA and PayloadB can extend Payload as shown below:

public abstract class Payload {

     private long createTimestamp;
     private String key;
     // some other fields are here
     // getters and setters here
}

public class PayloadA extends Payload  {
    //add existing code
}
public class PayloadB extends Payload {
    //add existing code
}

private void process(Payload payload) {

     //Depending upon the object passed, fields will be set for A or B       

     payload.setCreateTimestamp(ADD_DATA1);
     payload.setKey(ADD_DATA2);
     //set other fields

    //if(payload instanceof PayloadA) {
        //payloadA
    //}
}

How can I figure out whether payload is PayloadA or PayloadB inside process()?

You can find that using instanceof like payload instanceof PayloadA as shown above. But, in general, it is not a good idea to code using of instanceof checks, so don't prefer to use it unless it can't be avoided.

Should I have two different builders here or one big builder here doing everything?

As per your provided code above, the fields are quite PayloadA and PayloadB different, so better to keep separate beans and respective builders.

UPDATE: I need to figure out what type of payload it is and basis on that I need to set values for key variable?

Inside setKey() will be called on the object type passed to the process(Payload payload) (polymorphism, one of the fundamental principles of OOP) i.e., if you pass PayloadA object from run() method, setKey() on PayloadA object will be called. To summarise, you don't need instanceof checks at all for this. It is upto your requirement where do you want to set the Key, it could be inside process() method (if you have some other dependencies to generate the key) or it could be done as suggested by @Roberto

Upvotes: 1

Related Questions