1
\$\begingroup\$

I am studying Gang Of Four design patterns and I want to implement a builder pattern in Java.

It seems there are two options to build a builder pattern:

  1. Using two different classes: A target class and a builder class
  2. Using a nested static inner builder class inside the target class

Using builder class as a separate class

On both approaches, the target class member variables must be final, thus the target class instances will be immutable.

In this approach, target class will have two constructors. The first constructor will include all member variables of the class as a parameter. And second constructor will take one parameter of its own type, that which builder.build() method returns by using the first constructor of the target class.

Domain Object Class

package impl.first;

public class DomainObject {

    private final int id;
    private final String name;
    private final String surname;
    private final boolean isMarried;

    // includes all member variables
    public DomainObject(int id, String name, String surname, boolean isMarried) {
        this.id = id;
        this.name = name;
        this.surname = surname;
        this.isMarried = isMarried;
    }

    public DomainObject(DomainObject domainObject) {
        this.id = domainObject.getId();
        this.name = domainObject.getName();
        this.surname = domainObject.getSurname();
        this.isMarried = domainObject.isMarried();
    }

    // getters
    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getSurname() {
        return surname;
    }

    public boolean isMarried() {
        return isMarried;
    }
}

Domain Object Builder Class

package impl.first;

public final class DomainObjectBuilder {

    private int id;
    private String name;
    private String surname;
    private boolean isMarried;

    // No-arg Default Constructor
    public DomainObjectBuilder() { }

    public DomainObject build() {
        return new DomainObject(id, name, surname, isMarried);
    }

    public DomainObjectBuilder id(int id) {
        this.id = id;
        return this;
    }

    public DomainObjectBuilder name(String name) {
        this.name = name;
        return this;
    }

    public DomainObjectBuilder surname(String surname) {
        this.surname = surname;
        return this;
    }

    public DomainObjectBuilder isMarried(boolean isMarried) {
        this.isMarried = isMarried;
        return this;
    }

}

Demo code

package impl.first;

public class DomainObjectDemo {

    public static void main(String[] args) {
        DomainObjectBuilder builder = new DomainObjectBuilder();
        DomainObject dom;

        // Test #1 : None of member variables are set
        System.out.println("Test #1 : None of member variables are set");
        System.out.println("******************************************\n");

        dom = new DomainObject(builder.build());

        printDomObject(dom);

        // Test #2 : Half of objects are set
        System.out.println("Test #2 : Half of objects are set");
        System.out.println("*********************************\n");

        builder.id(1299).name("Levent").surname("Divilioglu").isMarried(false);
        dom = new DomainObject(builder.build());

        printDomObject(dom);

        // Test #3 : All of objects are set
        System.out.println("Test #3 : All of objects are set");
        System.out.println("********************************\n");

        builder.id(1299).name("Levent").surname("Divilioglu").isMarried(false);
        dom = new DomainObject(builder.build());

        printDomObject(dom);
    }

    public static void printDomObject(DomainObject dom) {
        System.out.println("Instance HashCode: " + dom.hashCode());
        System.out.println("Id               : " + dom.getId());
        System.out.println("Name             : " + dom.getName());
        System.out.println("Surname          : " + dom.getSurname());
        System.out.println("isMarried        : " + dom.isMarried());
        System.out.println();
    }
}

Output

Test #1 : None of member variables are set
******************************************

Instance HashCode: 705927765
Id               : 0
Name             : null
Surname          : null
isMarried        : false

Test #2 : Half of objects are set
*********************************

Instance HashCode: 366712642
Id               : 1299
Name             : Levent
Surname          : Divilioglu
isMarried        : false

Test #3 : All of objects are set
********************************

Instance HashCode: 1829164700
Id               : 1299
Name             : Levent
Surname          : Divilioglu
isMarried        : false

Using builder class as an inner static class

Domain object with inner static builder class

package impl.second;

public class DomainObject {

    private final int id;
    private final String name;
    private final String surname;
    private final boolean isMarried;

    public DomainObject(Builder domainObject) {
        this.id = domainObject.id;
        this.name = domainObject.name;
        this.surname = domainObject.surname;
        this.isMarried = domainObject.isMarried;
    }

    public static class Builder {
        private int id;
        private String name;
        private String surname;
        private boolean isMarried;

        // No-arg Default Constructor
        public Builder() { }

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

        public Builder id(int id) {
            this.id = id;
            return this;
        }

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

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

        public Builder isMarried(boolean isMarried) {
            this.isMarried = isMarried;
            return this;
        }
    }

    // getters
    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getSurname() {
        return surname;
    }

    public boolean isMarried() {
        return isMarried;
    }
}

Demo code

package impl.second;

public class DomainObjectDemo {

    public static void main(String[] args) {
        DomainObject.Builder builder = new DomainObject.Builder();
        DomainObject dom;

        // Test #1 : None of member variables are set
        System.out.println("Test #1 : None of member variables are set");
        System.out.println("******************************************\n");

        dom = builder.build();

        printDomObject(dom);

        // Test #2 : Half of objects are set
        System.out.println("Test #2 : Half of objects are set");
        System.out.println("*********************************\n");

        builder.id(1299).name("Levent").surname("Divilioglu").isMarried(false);
        dom = builder.build();

        printDomObject(dom);

        // Test #3 : All of objects are set
        System.out.println("Test #3 : All of objects are set");
        System.out.println("********************************\n");

        builder.id(1299).name("Levent").surname("Divilioglu").isMarried(false);
        dom = builder.build();

        printDomObject(dom);
    }

    public static void printDomObject(DomainObject dom) {
        System.out.println("Instance HashCode: " + dom.hashCode());
        System.out.println("Id               : " + dom.getId());
        System.out.println("Name             : " + dom.getName());
        System.out.println("Surname          : " + dom.getSurname());
        System.out.println("isMarried        : " + dom.isMarried());
        System.out.println();
    }
}

Output

Test #1 : None of member variables are set
******************************************

Instance HashCode: 705927765
Id               : 0
Name             : null
Surname          : null
isMarried        : false

Test #2 : Half of objects are set
*********************************

Instance HashCode: 366712642
Id               : 1299
Name             : Levent
Surname          : Divilioglu
isMarried        : false

Test #3 : All of objects are set
********************************

Instance HashCode: 1829164700
Id               : 1299
Name             : Levent
Surname          : Divilioglu
isMarried        : false

As it is obvious, on two different approach, three Domain Object instances are have different unique hashcodes thus each builder returns unique class instances.

What is the best practice, what are the pros and cons of each approach?

\$\endgroup\$
2
\$\begingroup\$

On both approaches, the target class member variables must be final, thus the target class instances will be immutable.

Nothing enforces that (text in black). But, it's always recommended to stay immutable unless there is some reason not to.

Your target class has public constructors which means anyone can create the object without even using builder. Those should be private and only builder should be allowed to build the object for that particular target.

Builder pattern is usually used when there are a lot of parameters and they are optional as well. Good thing about your code is you have provided only accessors from your target class which restricts user of the API to get only the data but not mutate it.

Advantage of using inner static class is the readibility as per my opinion. e.g. DomainObject.Builder.id(1).name("Test").build();

instead

DomainBuilder builder = new DomainBuilder();
builder.id(1).name("Test").build();
\$\endgroup\$
  • \$\begingroup\$ Thanks for giving a review, I voted for that however, in both cases the the class instances are immutable. Domain objects member variables are all final, and once an object instance is created, the state never changes. Thus I don't understand why you have said "none of the approach enforces immutability" This is why I try to learn builder pattern instead of using getters with POJO. \$\endgroup\$ – Levent Divilioglu May 3 '16 at 11:52
  • 1
    \$\begingroup\$ @LeventDivilioglu Updated the answer to be more clear, what I meant when I said, Nothing enforces that. It means you can go mutable as well. It's not about builder pattern that enforces immutability, it's the design choice. \$\endgroup\$ – java8.being May 3 '16 at 11:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.