When to Use:
My use case is to build one or more immutable data models that inherit the same abstract class. The data model usually has too many properties to initialize them in a constructor so it's the excellent time to use a builder pattern.How to Do It:
You may find a couple of examples to do a builder pattern with subclasses, but here I wanna have a simple and clean implementation that aims at data models.Let's give it a try...
Parent Class without Builder: PurchaseItem
// Take three properties for example
public abstract class PurchaseItem implements Serializable {
private final String itemNo;
private final String itemName;
private final Date itemCreateDate;
protected <T extends PurchaseItem> PurchaseItem(Builder<T> builder) {
this.itemNo = builder.itemNo();
this.itemName = builder.itemName();
this.itemCreateDate = builder.itemCreateDate();
}
public String getItemNo() {
return itemNo;
}
public String getItemName() {
return itemName;
}
public Date getItemCreateDate() {
return (itemCreateDate != null) ? new Date(itemCreateDate.getTime()) : null;
}
// omit hashCode, equals, toString, serialVersionUID...
}
Child Class without Builder: GameSubscription
// An immutable class
public final class GameSubscription extends PurchaseItem {
private final Long duration;
private final DurationUnit durationUnit;
public enum DurationUnit {
MONTH, UNDEFINED
}
private GameSubscription(Builder builder) {
super(builder);
this.duration = builder.duration();
this.durationUnit = builder.durationUnit();
}
public Long getDuration() {
return duration;
}
public DurationUnit getDurationUnit() {
return durationUnit;
}
// omit hashCode, equals, toString, serialVersionUID...
}
I think the code is pretty self-explained. The PurchaseItem class defines a bunch of common properties used by its subclasses. However, there is no setter provided so let's see how a builder involves in...
Parent Class with Builder: PurchaseItem
public abstract class PurchaseItem implements Serializable {
// omit properties, getters
public static class Builder<T extends PurchaseItem> {
private final Class<T> itemClass;
// Declare properties to avoid complaining private access in Java 7
private String itemNo;
private String itemName;
private Date itemCreateDate;
protected Builder(Class<T> itemClass) {
this.itemClass = itemClass;
}
public Builder<T> itemNo(String itemNo) {
this.itemNo = itemNo;
return this;
}
public Builder<T> itemName(String itemName) {
this.itemName = itemName;
return this;
}
public Builder<T> itemCreateDate(Date itemCreateDate) {
if (itemCreateDate != null) {
this.itemCreateDate = new Date(itemCreateDate.getTime());
}
return this;
}
public T build() {
try {
Constructor<T> constructor = null;
constructor = itemClass.getDeclaredConstructor(getClass());
constructor.setAccessible(true);
return constructor.newInstance(this);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
The parent builder provides a constructor for a generic subclass so the parent builder can set the properties belong to it. Also, the parent builder provides a build method to get the subclass.
Child Class with Builder: GameSubscription
public final class GameSubscription extends PurchaseItem {
// omit properties, getters
public static final class Builder extends PurchaseItem.Builder<GameSubscription> {
private Long duration;
private DurationUnit durationUnit;
public Builder() {
super(GameSubscription.class);
}
public Builder duration(Long duration) {
this.duration = duration;
return this;
}
public Builder durationUnit(DurationUnit durationUnit) {
this.durationUnit = durationUnit;
return this;
}
}
}
The subclass builder can do set properties easily. It just needs to extend the parent builder and provide a subclass type. Then it's done!
How to Use It:
The example shows how to create an immutable object from a domain object.
class FooUtils {
public static GameSubscription toGameSubscription(com.foo.domain.GameSubscription domainSub) {
if (domainSub == null) {
return null;
}
return toPurchaseItem(new GameSubscription.Builder()
.duration(domainSub.getDuration())
.durationUnit(toDurationUnit(domainSub.getDurationUnit())), domainSub)
.build();
}
private static <T extends PurchaseItem> PurchaseItem.Builder<T> toPurchaseItem(
PurchaseItem.Builder<T> builder, com.foo.domain.PurchaseItem domainItem) {
return builder
.itemNo(domainItem.getId())
.itemName(domainItem.getItemName())
.itemCreateDate(copyDate(domainItem.getItemCreateDate()));
}
}
Here I made a toPurchaseItem method to make more reusable for different subclasses. The interesting part is that the visibility of a subclass builder is different from a parent builder. So the subclass builder has to be used first; otherwise, once the parent builder is returned, you will not be able to set subclass properties.
That's it. Hope it's simple enough... :D