概要
- デザインパターンの1つ「Builderパターン」のJavaソースコードをWeb上で自動生成できるツールを作って公開しましたのでご紹介です。
- Effective Java、GoF、Telescoping Constructor、Lombok それぞれのBuilderパターンを瞬間的に生成します
使い方
アプリは以下となります
(HTML/CSS/JSだけで作っているのでサーバーは使っていません)
以下のように、Definitionのところ「型 変数名」を列挙していくと、右側のコードにリアルタイムに反映されます。
終わったら 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パターン
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
}
}
使うときはこんな風にします
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でしょう。
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);
}
}
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();
}
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;
}
}
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<>());
}
}
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パターンの出番ですね)
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**アノテーションをつけたときに自動生成されるものと同等のコードを本ツールにて生成することができます。
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パターンを使うときはこんな風にします
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.javaの
construct
メソッドに仮のコードを生成するようにしました。
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)に変更しました。基本的にはシンプルなテンプレートマッチで生成しています。