Saturday, May 19, 2012

Java: Builder Pattern in Inheritance

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

3 comments:

Andrew Ben said...

I think it is not good solution. It is not the solution for immutable object. You build() method returns the reference to the instance that can be changed later with setter methods of the Builder class. Goal of the builder is to create new instance every time the build method is called.

Andrew Ben said...
This comment has been removed by the author.
Jerry Meng said...

Thanks for the advise. I updated the code a little bit to make build method return new instance every time it's called; also change the way to assign properties in the builder to avoid complaining private access in JDK 1.7.