LoginSignup
4
0

More than 3 years have passed since last update.

必須プロパティのセットを強要するBuilderパターン

Last updated at Posted at 2019-08-04

概要

  • オブジェクトの生成時にMandatory(必須)のプロパティ必ずセットさせたい場合に使うBuilderパターンです
  • さらに決められた順序でしかプロパティをセットできないようにオブジェクト生成シーケンスに制約を設けるパターンです
  • Effective Java,GoF,Lombokとも異なるパターンです

環境

  • Java8以降

対象とするオブジェクトとプロパティ(フィールド)

あるプロパティをもつオブジェクトをつくりたいとき
今回は例として Person(人間) というオブジェクトについて考える

  • 必須でセットさせたいプロパティ
    • String name(名前)
    • Integer age(年齢)
    • String gender(性別)
    • Integer height(身長)
  • 任意でOKなプロパティ
    • String eyeColor(瞳の色)
    • String hairColor(髪の色)
    • String hobby(趣味)

コード

Builderのコードは以下のとおり。

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
  }
}

ポイント

  • 必須となるプロパティのsetterメソッドごとに個別のBuilderを割り振る
  • setterの戻り値に次のメソッドのBuilderを返していく
  • このようにプロパティごとにBuilderを互い違い(ツイストさせる)に返していく

利用側のコード

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

メリット

  • 必須のプロパティをすべて指定しないとbuildできないようになっている
  • 必須のプロパティを決められた順番かつ明示的(setterを呼ぶことで)に設定できる
    逆にいえば、以下のTelescoping Constructorのように引数を列挙していくやり方だとプロパティの数が増えるにつけて、利用側のコードの見通しが悪くなっていく。
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; 
  }

デメリット

  • (利用側のコードではなく)
    Builder側のコードが複雑になり見通しがわるくなる
  • ボイラープレート的コードを書くのに苦痛をともなう(※)

(※)デメリット緩和のため自動生成ツールをつくりました
https://riversun.github.io/java-builder/

まとめ

  • オブジェクト生成時に決められた初期化シーケンスを強要するBuilderパターンを紹介しました
  • このパターンはバリエーションも多くinterfaceを用いて実装する方法やBuilderを明示的に生成せずにやる方法なども利用されているようです。

関連記事

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0