Inna
Inna

Reputation: 77

Builder pattern

I need to implement Builder pattern without static nested classes. What is the best approach for doing it if I have inheritance? Let's imagine I have the following classes.

public class Car {
   private String brand;
   private String speed; 
   //getters an setters  
}

public class PassengerCar extends Car{
   private String capacity; 
   //getters an setters  
}

public class Truck extends Car{
   private String length; 
   //getters an setters  
}

Is it better to create one Builder class that will responsible for setting values of PassengerCar and Truck or we need 3 additional classes, CarBuilder, PassengerCarBuilder extends CarBuilder and TruckBuilder extends CarBuilder?

Upvotes: 3

Views: 1272

Answers (3)

fps
fps

Reputation: 34450

The correct approach would be to have one builder per class. I have seen two different builder implementations, let's call them lazy and eager (maybe one of them is not a strict builder, but both of them actually build instances).

Here are lazy builders for Cars and Trucks:

public abstract class AbstractLazyCarBuilder<T extends Car, B extends AbstractLazyCarBuilder<T, B>> {

    private String brand;

    private String speed;

    public B brand(String brand) {
        this.brand = brand;
        return (B) this;
    }

    public B speed(String speed) {
        this.speed = speed;
        return (B) this;
    }

    public T build() {
        T car = this.create();
        this.fill(car);
        return car;
    }

    protected abstract T create();

    protected void fill(T car) {
        car.setBrand(this.brand);
        car.setSpeed(this.speed);
    }
}

public class LazyCarBuilder extends AbstractLazyCarBuilder<Car, LazyCarBuilder> {

    @Override
    protected Car create() {
        return new Car();
    }
}

public class LazyTruckBuilder extends AbstractLazyCarBuilder<Truck, LazyTruckBuilder> {

    private String length;

    public LazyTruckBuilder length(String length) {
        this.length = length;
        return this;
    }

    @Override
    protected Truck create() {
        return new Truck();
    }

    @Override
    protected void fill(Truck truck) {
        super.fill(truck); // very important! fills truck with car's common attributes 
        truck.setLength(this.length);
    }
}

Usage:

Truck truck = new LazyTruckBuilder().brand("ford").speed("40").length("30").build();

It might not be the standard builder implementation. It has two generic parameters: the type of the object being built and the type of the builder itself. This last one is to avoid casting the returned object when using the builder methods.

It has a create() method that returns the empty specific instance (could have also been created by reflection) and a fill() method that sets all the attributes to the created object. This builder is lazy in the sense that the object is created and initialized when you call build() on the builder.

The eager version of this builder should use reflection to create the object being built:

public abstract class AbstractEagerCarBuilder<T extends Car, B extends AbstractEagerCarBuilder<T, B>> {

    protected final T instance; // needs to be seen by subclasses

    protected AbstractEagerCarBuilder() {
        try {
            // Reflection magic to get type of specific car
            ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass();
            Class<T> clazz = (Class<T>) type.getActualTypeArguments()[0];
            // Create the specific car by reflection
            this.instance = clazz.getConstructor().newInstance();
        } catch (Exception e) {
            throw new RuntimeException("Could not create specific instance", e);
        }
    }

    public B brand(String brand) {
        this.instance.setBrand(brand);
        return (B) this;
    }

    public B speed(String speed) {
        this.instance.setSpeed(speed);
        return (B) this;
    }

    public T build() {
        return this.instance;
    }
}

public class EagerCarBuilder extends AbstractEagerCarBuilder<Car, EagerCarBuilder> {
    // empty: just pass generic parameters
}

public class EagerTruckBuilder extends AbstractEagerCarBuilder<Truck, EagerTruckBuilder> {

    private String length;

    public EagerTruckBuilder length(String length) {
        this.instance.setLength = length;
        return this;
    }
}

Usage:

Truck truck = new EagerTruckBuilder().brand("gmc").speed("45").length("32").build();

Here, the truck instance is actually created at builder's creation time. Then, builder methods fill the eagerly created instance with attributes, one by one.

Whether to use one or the other, is up to you. Plase let me know if this has errors, as I couldn't test it, as well as if you have any questions (I'm so used to this kind of code that I might be lacking some useful explanation).

Upvotes: 2

Manu
Manu

Reputation: 4137

According to the Builder pattern, you'd have:

  1. A base builder class, let's say Builder, possibly abstract
  2. A concrete builder class for each concrete class you want to instantiate via builder

http://upload.wikimedia.org/wikipedia/commons/thumb/f/f3/Builder_UML_class_diagram.svg/500px-Builder_UML_class_diagram.svg.png

So, the buildPart methods (in your case setters) of the builders should be organized and assigned to the correct class in a hierarchy of builders; i.e., if two builders share the same implementations of a particular buildPart method, then probably those implementations should be merged and moved to a common upper-level class (i.e. a class that both builders extend).

This way you avoid duplicate code, inconsistencies in the building process, and you end up having a sound structure for the builders.

Note 1: I don't understand why you have getters in your classes.

Note: 2I would avoid calling "setters" method to build the objects, but I would use a constructor (possibly @Deprecated, so that you discourage someone to use it, as the corresponding builder should be used). The reason is that, during the building process you may have objects in an inconsistent state (i.e. objects that are just partially built). Leveraging a constructor, it is invoked to completely instantiate an object when its whole state can be initialized to a sound value.

Upvotes: 0

Ahmed Khalaf
Ahmed Khalaf

Reputation: 1419

You can tweak the setter methods a bit to achieve what you want. Make the setter methods of the super classes parameterized to accept a generic class that extends the base class.

public class CompactBuilderPatternDemo {

public static class A {

    private String a;

    public String getA() {
        return a;
    }

    public <T extends A> T setA(String a, Class<T> childClass) {
        this.a = a;
        return childClass.cast(this);
    }

    public static class B extends A {

        private String b;

        public String getB() {
            return b;
        }

        public String getAB() {
            return super.getA() + b;
        }

        public B setB(String b) {
            this.b = b;
            return this;
        }
    }
}

public static void main(String[] args) {
    B b = new B().setA("aaa", B.class).setB("bbb");

    System.out.println(b.getAB());
    }
}

A similar topic is discussed here.

Upvotes: 0

Related Questions