Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement the builder pattern in Java 8?

Implement the builder pattern prior to Java 8 has lots of tedious, nearly duplicated code; the builder itself is typically boilerplate code. Some duplicate code detectors consider nearly each method of a pre-Java 8 builder as a copy of every other method.

Consider the following pre-Java 8 builder pattern:

public class Person {      private String name;     private int age;      public String getName() {         return name;     }      public void setName(String name) {         this.name = name;     }      public int getAge() {         return age;     }      public void setAge(int age) {         this.age = age;     } }  public class PersonBuilder {          private static class PersonState {         public String name;         public int age;     }          private PersonState  state = new PersonState();          public PersonBuilder withName(String name) {         state.name = name;         return this;     }          public PersonBuilder withAge(int age) {         state.age = age;         return this;     }          public Person build() {         Person person = new Person();         person.setAge(state.age);         person.setName(state.name);         state = new PersonState();         return person;     } } 

How can the builder pattern be implemented using Java 8?

like image 741
SpaceTrucker Avatar asked Jul 31 '15 20:07

SpaceTrucker


People also ask

How do you implement a builder pattern?

Implementation : In Builder pattern, we have a inner static class named Builder inside our Server class with instance fields for that class and also have a factory method to return an new instance of Builder class on every invocation. The setter methods will now return Builder class reference.

What is builder () method in java?

Builder is a creational design pattern, which allows constructing complex objects step by step. Unlike other creational patterns, Builder doesn't require products to have a common interface. That makes it possible to produce different products using the same construction process.

How do you instantiate a builder class in java?

First, you create an instance of the Builder class by passing the mandatory fields to its constructor. Then, you set the values for the optional fields by calling the setter-like methods of the Builder class. Once you have set all the fields, you call the build method on the Builder instance.


Video Answer


1 Answers

The GenericBuilder

The idea for building mutable objects (immutable objects are addressed later on) is to use method references to setters of the instance that should be built. This leads us to a generic builder that is capable of building every POJO with a default constructor - one builder to rule them all ;-)

The implementation is this:

public class GenericBuilder<T> {      private final Supplier<T> instantiator;      private List<Consumer<T>> instanceModifiers = new ArrayList<>();      public GenericBuilder(Supplier<T> instantiator) {         this.instantiator = instantiator;     }      public static <T> GenericBuilder<T> of(Supplier<T> instantiator) {         return new GenericBuilder<T>(instantiator);     }      public <U> GenericBuilder<T> with(BiConsumer<T, U> consumer, U value) {         Consumer<T> c = instance -> consumer.accept(instance, value);         instanceModifiers.add(c);         return this;     }      public T build() {         T value = instantiator.get();         instanceModifiers.forEach(modifier -> modifier.accept(value));         instanceModifiers.clear();         return value;     } } 

The builder is constructed with a supplier that creates new instances and then those instances are modified by the modifications specified with the with method.

The GenericBuilder would be used for Person like this:

Person value = GenericBuilder.of(Person::new)             .with(Person::setName, "Otto").with(Person::setAge, 5).build(); 

Properties and further Usages

But there is more about that builder to discover.

For example, the above implementation clears the modifiers. This could be moved into its own method. Therefore, the builder would keep its state between modifications and it would be easy create multiple equal instances. Or, depending on the nature of an instanceModifier, a list of varying objects. For example, an instanceModifier could read its value from an increasing counter.

Continuing with this thought, we could implement a fork method that would return a new clone of the GenericBuilder instance that it is called on. This is easily possible because the state of the builder is just the instantiator and the list of instanceModifiers. From there on, both builders could be altered with some other instanceModifiers. They would share the same basis and have some additional state set on built instances.

The last point I consider especially helpful when needing heavy entities for unit or even integration tests in enterprise applications. There would be no god-object for entities, but for builders instead.

The GenericBuilder can also replace the need for different test value factories. In my current project, there are many factories used for creating test instances. The code is tightly coupled to different test scenarios and it is difficult to extract portions of a test factory for reuse in another test factory in a slightly different scenario. With the GenericBuilder, reusing this becomes much easier as there is only a specific list of instanceModifiers.

To verify that created instances are valid, the GenericBuilder could be initialized with a set of predicates, which are verified in the build method after all instanceModifiers are run.

public T build() {     T value = instantiator.get();     instanceModifiers.forEach(modifier -> modifier.accept(value));     verifyPredicates(value);     instanceModifiers.clear();     return value; }  private void verifyPredicates(T value) {     List<Predicate<T>> violated = predicates.stream()             .filter(e -> !e.test(value)).collect(Collectors.toList());     if (!violated.isEmpty()) {         throw new IllegalStateException(value.toString()                 + " violates predicates " + violated);     } } 

Immutable object creation

To use the above scheme for the creation of immutable objects, extract the state of the immutable object into a mutable object and use the instantiator and builder to operate on the mutable state object. Then, add a function that will create a new immutable instance for the mutable state. However, this requires that the immutable object either has its state encapsulated like this or it be changed in that fashion (basically applying parameter object pattern to its constructor).

This is in some way different than a builder was used in pre-java-8 times. There, the builder itself was the mutable object that created a new instance at the end. Now, we have a separation of the state a builder keeps in a mutable object and the builder functionality itself.

In essence
Stop writing boilerplate builder patterns and get productive using the GenericBuilder.

like image 139
SpaceTrucker Avatar answered Sep 23 '22 13:09

SpaceTrucker