[JAVA] Builder pattern that forces a set of required properties

Overview

--Used when ** Mandatory (required) property ** </ font> is ** must be set ** </ font> when creating an object ** Builder Pattern ** --Furthermore, it is a pattern that restricts the object creation sequence so that properties can be set only in a fixed order. --Effective Java, GoF, Lombok are also different patterns

environment

--Java 8 or later

Target objects and properties (fields)

When you want to create an object with a certain property This time, let's consider an object called Person as an example.

-** Required property ** --String name --Integer age --String gender --Integer height -** Optional OK properties ** --String eyeColor --String hairColor --String hobby (hobby)

code

The builder code is below.

Person



package com.example;
import java.util.Optional;

public class Person {

  private final String name;// Required
  private final Integer age;// Required
  private final String gender;// Required
  private final Integer height;// Required
  private final Optional<String> eyeColor;// Optional(use Optional<String>)
  private final Optional<String> hairColor;// Optional(use Optional<String>)
  private final Optional<String> hobby;// Optional(use Optional<String>)

  Person(Builder.Builder1 builder) {
    this.name = builder.name; 
    this.age = builder.age; 
    this.gender = builder.gender; 
    this.height = builder.height; 
    this.eyeColor = builder.eyeColor; 
    this.hairColor = builder.hairColor; 
    this.hobby = builder.hobby; 
  }
  public static Builder builder() {
    return new Builder();
  }

  public static final class Builder {
    public Builder1 name(String name) {
      return new Builder1(name);
    }
    public static final class Builder1 {
      final String name;
      Integer age;
      String gender;
      Integer height;
      Optional<String> eyeColor;
      Optional<String> hairColor;
      Optional<String> hobby;

      private Builder1(String name) {
        this.name = name;
      }
      public Builder2 age(Integer age) {
        this.age = age;
        return new Builder2(Builder1.this);
      }
    }
    public static final class Builder2 {
      final Builder1 builder;

      private Builder2(Builder1 builder) {
        this.builder = builder;
      }
      public Builder3 gender(String gender) {
        this.builder.gender = gender;
        return new Builder3(this.builder);
      }
    }
    public static final class Builder3 {
      final Builder1 builder;

      private Builder3(Builder1 builder) {
        this.builder = builder;
      }
      public Builder4 height(Integer height) {
        this.builder.height = height;
        return new Builder4(this.builder);
      }
    }
    public static final class Builder4 {
      final Builder1 builder;

      private Builder4(Builder1 builder) {
        this.builder = builder;
      }
      public Builder4 eyeColor(String eyeColor){
        this.builder.eyeColor = Optional.of(eyeColor);
        return this;
      }
      public Builder4 hairColor(String hairColor){
        this.builder.hairColor = Optional.of(hairColor);
        return this;
      }
      public Builder4 hobby(String hobby){
        this.builder.hobby = Optional.of(hobby);
        return this;
      }
      public Person build() {
        return new Person(this.builder);
      }
    }
  }

  public String name() {
    return this.name;
  }
  public Integer age() {
    return this.age;
  }
  public String gender() {
    return this.gender;
  }
  public Integer height() {
    return this.height;
  }
  public Optional<String> eyeColor() {
    return this.eyeColor;
  }
  public Optional<String> hairColor() {
    return this.hairColor;
  }
  public Optional<String> hobby() {
    return this.hobby;
  }

  @Override
  public String toString() {
    return "Person(name=" + this.name + ", age=" + this.age + ", gender=" + this.gender + ", height=" + this.height + ", eyeColor=" + this.eyeColor + ", hairColor=" + this.hairColor + ", hobby=" + this.hobby + ")";
  }

  public void doSomething() {
      // do something
  }
}

point

--Assign a separate builder for each setter method for required properties --Returns the Builder of the next method as the return value of setter --In this way, the Builders are returned alternately (twisted) for each property.

User code

package test;
import com.example.Person;
public class Main {
    public static void main(String[] args) {
        Person person = Person.builder().name("Tom").age(20).gender("male").height(200).build();
        System.out.println(person);
    }
}

builder_man.gif

merit

--You cannot ** build ** unless you specify all the required properties. --Required properties can be set in a fixed order and explicitly (by calling the setter) Conversely, if you enumerate the arguments like the ** Telescoping Constructor ** below, the visibility of the code on the user side will worsen as the number of properties increases.

TelescopingConstructor


  public Person(String name, Integer age, String gender, Integer height, String eyeColor, String hairColor, String hobby) {
    this.name = name; 
    this.age = age; 
    this.gender = gender; 
    this.height = height; 
    this.eyeColor = eyeColor; 
    this.hairColor = hairColor; 
    this.hobby = hobby; 
  }

Demerit

-(Not the user's code) The code on the builder side becomes complicated and the outlook becomes poor --It is painful to write boilerplate-like code (*)

(*) We have created an automatic generation tool to mitigate the disadvantages. https://riversun.github.io/java-builder/

Summary

--Introduced a Builder pattern that forces an initialization sequence determined when creating an object ――There are many variations of this pattern, and it seems that the method of implementing it using ** interface ** and the method of doing it without explicitly generating ** Builder ** are also used.

Related article

Recommended Posts