LoginSignup
8
2

More than 3 years have passed since last update.

人は悩む、人は作る、Builderパターンを ~JavaのBuilderパターンを自動生成するツール~

Last updated at Posted at 2019-07-31

概要

  • デザインパターンの1つ「Builderパターン」のJavaソースコードをWeb上自動生成できるツールを作って公開しましたのでご紹介です。
  • Effective JavaGoFTelescoping ConstructorLombok それぞれのBuilderパターンを瞬間的に生成します

使い方

アプリは以下となります

(HTML/CSS/JSだけで作っているのでサーバーは使っていません)

以下のように、Definitionのところ「型 変数名」を列挙していくと、右側のコードにリアルタイムに反映されます。
animation.gif

終わったら Copy to Clipboard を押してコードをコピーして、IDEやエディタに張り付ければおしまいです。

生成したコード例

以下は各種Builderパターンの生成例です。

入力した元データ(Definition)

以下は、コード生成をするために入力した内容です(クラス名や型・変数名など)

Definitionにあるテキストエリアに、以下のように、型 名前を入力して改行して列挙すればリアルタイムに右側にコードが自動生成されます。

package org.example
class Person
@NotNull String name
int age
String sex
List<String> hobby

@NotNullアノテーションを定義行の先頭に入れると、そのプロパティは必須プロパティとなり、buildのときにnullチェックをするコードが生成されます。

生成したEffective JavaのBuilderパターン

Person.java(ツールにより自動生成)
package org.example;
import java.util.ArrayList;
import java.util.List;

public class Person {

  private String name;
  private int age;
  private String sex;
  private List<String> hobby;

  public static class Builder {

    private String name;
    private int age;
    private String sex;
    private List<String> hobby = new ArrayList<String>();

    public Builder() {    
    }

    Builder(String name, int age, String sex, List<String> hobby) {    
      this.name = name; 
      this.age = age; 
      this.sex = sex; 
      this.hobby = hobby;             
    }

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

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

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

    public Builder hobby(List<String> hobby){
      this.hobby = hobby;
      return Builder.this;
    }

    public Builder addHobby(String hobby){
      this.hobby.add(hobby);
      return Builder.this;
    }

    public Person build() {
        if(this.name == null){
          throw new NullPointerException("The property \"name\" is null. "
              + "Please set the value by \"name()\". "
              + "The property \"name\" is required.");
        }

        return new Person(this);
    }
  }

  private Person(Builder builder) {
    this.name = builder.name; 
    this.age = builder.age; 
    this.sex = builder.sex; 
    this.hobby = builder.hobby;     
  }

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

使うときはこんな風にします

AppMain.java
public class AppMain {
    public static void main(String[] args) {
        new Person.Builder()
        .name("Tom")
        .age(18)
        .sex("male")
        .addHobby("programming")
        .addHobby("skiing")
        .build().doSomething();
    }
}

生成したGoFのBuilderパターン

GoFのBuilderパターンはもう少し登場人物が多く複数のクラスが生成されます。
メインに相当するAppMain.javaも生成します。
主に編集するのは実際のBuild条件を決めるDirector.javaでしょう。

Person.java(自動生成)
package org.example;
import java.util.ArrayList;
import java.util.List;

public class Person {

  private String name;
  private int age;
  private String sex;
  private List<String> hobby = new ArrayList<String>();

  public Person() {

  }

  public Person(String name, int age, String sex, List<String> hobby) {
    this.name = name; 
    this.age = age; 
    this.sex = sex; 
    this.hobby = hobby; 
  }

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

  public String getName(){
    return this.name;
  }

  public Person setAge(int age){
    this.age = age;
    return Person.this;
  }

  public int getAge(){
    return this.age;
  }

  public Person setSex(String sex){
    this.sex = sex;
    return Person.this;
  }

  public String getSex(){
    return this.sex;
  }

  public Person setHobby(List<String> hobby){
    this.hobby = hobby;
    return Person.this;
  }

  public Person addHobby(String hobby){
    this.hobby.add(hobby);
    return Person.this;
  }

  public List<String> getHobby(){
    return this.hobby;
  }

  public void doSomething(){
    System.out.println("Person's properties");
    System.out.println("name="+name);
    System.out.println("age="+age);
    System.out.println("sex="+sex);
    System.out.println("hobby="+hobby);  
  }
}
Builder.java(自動生成)
package org.example;
import java.util.List;

public interface Builder {

  public void name(String name);
  public void age(int age);
  public void sex(String sex);
  public void hobby(List<String> hobby);

  Person getResult();
}
PersonBuilder.java(自動生成)
package org.example;
import java.util.List;

public class PersonBuilder implements Builder {
  private Person person;

  public PersonBuilder() {
    this.person = new Person();
  }

  @Override
  public void name(String name) {
    this.person.setName(name);
  }

  @Override
  public void age(int age) {
    this.person.setAge(age);
  }

  @Override
  public void sex(String sex) {
    this.person.setSex(sex);
  }

  @Override
  public void hobby(List<String> hobby) {
    this.person.setHobby(hobby);
  }


  @Override
  public Person getResult() {

    if(this.person.getName() == null){
      throw new NullPointerException("The property \"name\" is null. "
          + "Please set the value by \"builder.name()\" at Director class. "
          + "The property \"name\" is required.");
    }

    return this.person;
  }
}
Director.java(自動生成)
package org.example;

public class Director {

  private Builder builder;

  public Director(Builder builder) {
    this.builder = builder;
  }

  public void construct() {
    builder.name("something"); // required property
    builder.age(0); // optional property
    builder.sex("something"); // optional property
    //builder.hobby(new ArrayList<>());
  }
}
AppMain.java(自動生成)
package org.example;

public class AppMain {

  public static void main(String[] args) {

    Builder builder = new PersonBuilder();
    Director director = new Director(builder);
    director.construct();
    Person person = builder.getResult();
    person.doSomething();

  }
}

生成したTelescoping Constructor

Builderパターンというわけではないと思いますが、オブジェクトを生成するときにコンストラクターに複数の引数をとる、ごく一般的なパターンです。

(引数が増えていくと、見通しが悪くなっていくのでBuilderパターンの出番ですね)

Person.java
package org.example;
import java.util.ArrayList;
import java.util.List;

public class Person {

  private String name;
  private int age;
  private String sex;
  private List<String> hobby = new ArrayList<String>();

  public Person() {

  }

  public Person(String name, int age, String sex, List<String> hobby) {
    this.name = name; 
    this.age = age; 
    this.sex = sex; 
    this.hobby = hobby; 
  }

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

  public String getName() {
    return this.name;
  }

  public Person setAge(int age) {
    this.age = age;
    return Person.this;
  }

  public int getAge() {
    return this.age;
  }

  public Person setSex(String sex) {
    this.sex = sex;
    return Person.this;
  }

  public String getSex() {
    return this.sex;
  }

  public Person setHobby(List<String> hobby) {
    this.hobby = hobby;
    return Person.this;
  }

  public Person addHobby(String hobby){
    this.hobby.add(hobby);
    return Person.this;
  }

  public List<String> getHobby() {
    return this.hobby;
  }

}

生成したLombokのBuilderパターン

Lombokに@Builderアノテーションをつけたときに自動生成されるものと同等のコードを本ツールにて生成することができます。

Person.java(自動生成)
package org.example;
import java.util.ArrayList;
import java.util.List;

public class Person {

  private String name;
  private int age;
  private String sex;
  private List<String> hobby;

  Person(String name, int age, String sex, List<String> hobby) {

    if(name == null){
      throw new NullPointerException("The property \"name\" is null. "
          + "Please set the value by \"name()\". "
          + "The property \"name\" is required.");
    }

    this.name = name; 
    this.age = age; 
    this.sex = sex; 
    this.hobby = hobby; 
  }

  public static PersonBuilder builder(){
    return new PersonBuilder();
  }  
  public static class PersonBuilder {

    private String name;
    private int age;
    private String sex;
    private List<String> hobby = new ArrayList<String>();

    PersonBuilder() {    
    }

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

    public PersonBuilder age(int age){
      this.age = age;
      return PersonBuilder.this;
    }

    public PersonBuilder sex(String sex){
      this.sex = sex;
      return PersonBuilder.this;
    }

    public PersonBuilder hobby(List<String> hobby){
      this.hobby = hobby;
      return PersonBuilder.this;
    }

    public PersonBuilder addHobby(String hobby){
      this.hobby.add(hobby);
      return PersonBuilder.this;
    }

    public Person build() {
      return new Person(this.name, this.age, this.sex, this.hobby);
    }
    @Override
    public String toString() {
      return "Person.PersonBuilder(name=" + this.name + ", age=" + this.age + ", sex=" + this.sex + ", hobby=" + this.hobby + ")";
    }
  }

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

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

LombokのBuilderパターンを使うときはこんな風にします

AppMain.java
public class AppMain {
    public static void main(String[] args) {
        System.out.println(Person.builder().name("Tom").age(12).sex("male").build());
    }
}

実行結果

Person(name=Tom, age=12, sex=male, hobby=[])

自動生成のポイントメモ

細かい生成設定をせずに、かつ、生成されたコードがなるべくそのまま使えるようなTypicalなコード出力をめざして以下の工夫をいれています。

Effective Javaテンプレート

  • List<String> hobby;のようなList<>の場合はnew ArrayList();での初期化と、addHobby(String hobby)のようにアイテムの個別追加ができるメソッドも生成するようにしました。
  • intのようなprimitive型の変数の場合はnullチェックコードは生成しないようにしました。

GoFテンプレート

  • GoFのBuilderパターンはオブジェクトの構築がほかのものより複雑なので、AppMain.java(メインクラス)を生成するようにしました。
  • Director.javaconstructメソッドに仮のコードを生成するようにしました。
  public void construct() {
    builder.name("something");
    builder.age(0);
    builder.sex("something");
    //builder.hobby(new ArrayList<>());
  }

Telescoping Constructorテンプレート

  • Setterメソッドは当初のJava Beansのように void setName(name)ではなく、Person setName(name)のようにしてメソッドのチェイン呼び出しができるようにしました。

(まだまだ改善の余地あり~)

まとめ

  • BuilderパターンのJavaコードが手軽に生成できるWebツールを作って公開しました。
  • 何かのお役に立てれば幸いです。
  • 世の中には、Lombokで生成させたり、IntelliJやEclipseのプラグインにもBuilderパターンを生成できるものがあるようです。
    (私は自前ツール愛好家なので作ること自体が楽しいですね)

(おまけ)

  • Javaコードを生成するんだから、Javaで作ろうと短絡的に考え https://github.com/riversun/java-builder-pattern-source-generator を作りましたが、コードの設計はそのままに途中からWeb版(JavaScript)に変更しました。基本的にはシンプルなテンプレートマッチで生成しています。
8
2
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
8
2