FreeBuilderを使用したBuilderパターンの自動生成

[[builder pattern]]
=== 1. 概要

このチュートリアルでは、https://freebuilder.inferred.org/ [FreeBuilderライブラリ]を使用して、Javaでビルダークラスを生成します。

[[design patterns]]
=== 2. ビルダーのデザインパターン

Builderは、オブジェクト指向言語で最も広く使用されているlink:/creational-design-patterns[Creation Design Patterns]の1つです。 *複雑なドメインオブジェクトのインスタンス化を抽象化し、インスタンスを作成するための流fluentなAPI *を提供します。 それにより、簡潔なドメイン層を維持するのに役立ちます。
その有用性にもかかわらず、ビルダーは一般に、特にJavaでの実装が複雑です。 より単純な値オブジェクトでも、多くの定型コードが必要です。

[[Builder java]]
=== 3. Javaでのビルダーの実装

FreeBuilderに進む前に、__ Employee __classのボイラープレートビルダーを実装しましょう。
public class Employee {

    private final String name;
    private final int age;
    private final String department;

    private Employee(String name, int age, String department) {
        this.name = name;
        this.age = age;
        this.department = department;
    }
}
そして、inner __Builder __class:
public static class Builder {

    private String name;
    private int age;
    private String department;

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

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

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

    public Employee build() {
        return new Employee(name, age, department);
    }
}
したがって、ビルダーを使用して__Employee __objectをインスタンス化できます。
Employee.Builder emplBuilder = new Employee.Builder();

Employee employee = emplBuilder
  .setName("baeldung")
  .setAge(12)
  .setDepartment("Builder Pattern")
  .build();
上記のように、*ビルダークラスを実装するには、多くの定型コードが必要です。*
後のセクションで、FreeBuilderがこの実装を即座に簡素化する方法について説明します。

[[freebuilder maven]]
=== 4. メーベン依存

FreeBuilderライブラリを追加するには、https://search.maven.org/search?q = g:org.inferred%20AND%20a:freebuilderを追加します
<dependency>
    <groupId>org.inferred</groupId>
    <artifactId>freebuilder</artifactId>
    <version>2.4.1</version>
</dependency>

5. _FreeBuilder_アノテーション

5.1. ビルダーの生成

FreeBuilderは、ビルダークラスを実装する際に開発者が定型コードを回避するのに役立つオープンソースライブラリです。 Javaの注釈処理を使用して、ビルダーパターンの具体的な実装を生成します。
前のセクションの__Employee __classに_ @ _ ** __ * FreeBuilder * __注釈を付けて、ビルダークラスが自動的に生成される方法を確認します。
@FreeBuilder
public interface Employee {

    String name();
    int age();
    String department();

    class Builder extends Employee_Builder {
    }
}
  • Employee がPOJOクラスではなく、** * interface * _になったことを指摘することが重要です。 さらに、an Employee objectのすべての属性がメソッドとして含まれています。

    このビルダーを引き続き使用する前に、コンパイルの問題を回避するためにIDEを構成する必要があります。 __FreeBuilder ___はコンパイル中に__Employee_Builder __classを自動的に生成するため、IDEは通常*行番号8で_ClassNotFoundException_を訴えます*。
    このような問題を回避するには、https://www.jetbrains.com/help/idea/configuring-annotation-processing.html [IntelliJ]またはhttps://help.eclipse.org/kepler/で注釈処理を有効にする必要があります。 index.jsp?topic =%2Forg.eclipse.jdt.doc.isv%2Fguide%2Fjdt_apt_getting_started.htm [Eclipse] *。 そしてその間に、FreeBuilderの注釈processor annotation_org.inferred.freebuilder.processor.Processor._を使用します。さらに、これらのソースファイルの生成に使用されるディレクトリは、https://www.jetbrains.com/help/ideaとしてマークする必要があります。 /content-roots.html [生成されたソースルート]。
    または、* mvn install_ *を実行してプロジェクトをビルドし、必要なビルダークラスを生成することもできます。
    最後に、プロジェクトをコンパイルして、__Employee.Builder __classを使用できるようになりました。
Employee.Builder builder = new Employee.Builder();

Employee employee = builder.name("baeldung")
  .age(10)
  .department("Builder Pattern")
  .build();
全体として、これと先ほど見たビルダークラスには2つの主な違いがあります。 まず、* __ Employee __classのすべての属性に値を設定する必要があります。 それ以外の場合、* _ * IllegalStateException * ._をスローします
FreeBuilderがオプション属性をどのように処理するかについては、後のセクションで説明します。
第二に、__E​​mployee.Builder ___のメソッド名はJavaBeanの命名規則に従っていません。 これについては、次のセクションで説明します。

[[freebuilder naming]]
==== 5.2. JavaBeanの命名規則

FreeBuilderがJavaBeanの命名規則に従うように強制するには、__Employee __でメソッドの名前を変更し、メソッドの前に* _ * get *:_を付ける必要があります。
@FreeBuilder
public interface Employee {

    String getName();
    int getAge();
    String getDepartment();

    class Builder extends Employee_Builder {
    }
}
これにより、JavaBeanの命名規則に従うゲッターとセッターが生成されます。
Employee employee = builder
  .setName("baeldung")
  .setAge(10)
  .setDepartment("Builder Pattern")
  .build();

[[freebuilder mapper]]
==== 5.3. マッパーメソッド

FreeBuilderは、ゲッターとセッターと組み合わせて、ビルダークラスにマッパーメソッドも追加します。 これらのマッパーメソッドは、https://www.baeldung.com/java-8-functional-interfaces [UnaryOperator]を入力として受け入れ、*開発者が複雑なフィールド値を計算できるようにします。
_Employee_クラスにも給与フィールドがあるとします:
@FreeBuilder
public interface Employee {
    Optional<Double> getSalaryInUSD();
}
ここで、入力として提供される給与の通貨を変換する必要があるとします。
long salaryInEuros = INPUT_SALARY_EUROS;
Employee.Builder builder = new Employee.Builder();

Employee employee = builder
  .setName("baeldung")
  .setAge(10)
  .mapSalaryInUSD(sal -> salaryInEuros * EUROS_TO_USD_RATIO)
  .build();
FreeBuilderは、すべてのフィールドにこのようなマッパーメソッドを提供します。

[[freebuilder constraints]]
=== 6. デフォルト値と制約チェック

[[freebuilder default]]
==== 6.1. デフォルト値の設定

これまで説明してきた_Employee.Builder_の実装では、クライアントがすべてのフィールドに値を渡すことを想定しています。 実際のところ、フィールドが欠落している場合、__IllegalStateException ___で初期化プロセスが失敗します。
このような失敗を回避するために、*フィールドのデフォルト値を設定するか、オプションにすることができます*。
__Employee.Builder __constructorでデフォルト値を設定できます。
@FreeBuilder
public interface Employee {

    // getter methods

    class Builder extends Employee_Builder {

        public Builder() {
            setDepartment("Builder Pattern");
        }
    }
}
そのため、コンストラクタにdefault __department ___を設定するだけです。 この値は、すべての__Employee __objectsに適用されます。

[[freebuilder constraints]]
==== 6.2. 制約チェック

通常、フィールド値には一定の制約があります。 たとえば、有効なメールには「@」が含まれているか、__ Employee ___の年齢が範囲内にある必要があります。
このような制約により、入力値の検証を行う必要があります。 * FreeBuilderでは、__setter ___ methods *をオーバーライドするだけでこれらの検証を追加できます。
@FreeBuilder
public interface Employee {

    // getter methods

    class Builder extends Employee_Builder {

        @Override
        public Builder setEmail(String email) {
            if (checkValidEmail(email))
                return super.setEmail(email);
            else
                throw new IllegalArgumentException("Invalid email");

        }

        private boolean checkValidEmail(String email) {
            return email.contains("@");
        }
    }
}

[[freebuilder optional]]
=== 7. オプション値

[[freebuilder optional]]
==== 7.1. _Optional_フィールドの使用

一部のオブジェクトにはオプションのフィールドが含まれ、その値は空またはnullになります。 * FreeBuilderでは、https://www.baeldung.com/java-optional [Java _Optional_]タイプを使用してこのようなフィールドを定義できます*:
@FreeBuilder
public interface Employee {

    String getName();
    int getAge();

    // other getters

    Optional<Boolean> getPermanent();

    Optional<String> getDateOfJoining();

    class Builder extends Employee_Builder {
    }
}
これで、__ Optional __fieldsの値の提供をスキップできます。
Employee employee = builder.setName("baeldung")
  .setAge(10)
  .setPermanent(true)
  .build();
特に、__ Optionalの代わりに_permanent_フィールドに値を渡しました。 __ * __dateOfJoining __fieldの値を設定していないため、it__Optional.empty()__が__Optional __fieldsのデフォルトになります*

[[freebuilder nullable]]
==== 7.2. _ @ Nullable_フィールドの使用

Javaで__null__sを処理するには_Optional_を使用することをお勧めしますが、FreeBuilderでは* usが_https://www.baeldung.com/java-avoid-null-check [@Nullable] _を使用して下位互換性を確保できます*:
@FreeBuilder
public interface Employee {

    String getName();
    int getAge();

    // other getter methods

    Optional<Boolean> getPermanent();
    Optional<String> getDateOfJoining();

    @Nullable String getCurrentProject();

    class Builder extends Employee_Builder {
    }
}
_https://www.baeldung.com/java-optional-return [Optional] _ link:/java-optional-return [場合によっては不適切です]の使用は別の理由ですビルダークラスに___Nullable __が推奨される理由。

[[freebuilder collections]]
=== 8. コレクションと地図

FreeBuilderは、コレクションとマップを特別にサポートしています。
@FreeBuilder
public interface Employee {

    String getName();
    int getAge();

    // other getter methods

    List<Long> getAccessTokens();
    Map<String, Long> getAssetsSerialIdMapping();


    class Builder extends Employee_Builder {
    }
}
FreeBuilderは、ビルダークラスのコレクションに入力要素を追加するための*便利なメソッドを追加します*:
Employee employee = builder.setName("baeldung")
  .setAge(10)
  .addAccessTokens(1221819L)
  .addAccessTokens(1223441L, 134567L)
  .build();
ビルダークラスには、変更不可能なリストを返す* __getAccessTokens()__methodもあります。 同様に、_Map:_
Employee employee = builder.setName("baeldung")
  .setAge(10)
  .addAccessTokens(1221819L)
  .addAccessTokens(1223441L, 134567L)
  .putAssetsSerialIdMapping("Laptop", 12345L)
  .build();
__Map ___for__getter __methodも、クライアントコードに*変更不可能なマップ*を返します。

[[freebuilder nested]]
=== 9. ネストされたビルダー

実際のアプリケーションでは、*ドメインエンティティの*多くの値オブジェクトをネストする必要があります*。 また、ネストされたオブジェクト自体がビルダーの実装を必要とする可能性があるため、FreeBuilderではネストされたビルド可能な型を使用できます。
たとえば、__Employee __classにネストされた複合型__Address ___があるとします。
@FreeBuilder
public interface Address {

    String getCity();

    class Builder extends Address_Builder {
    }
}
現在、FreeBuilderは、__ Address __typeと一緒に_Address.Builder_を入力として取得する__setter __methodsを生成します。
Address.Builder addressBuilder = new Address.Builder();
addressBuilder.setCity(CITY_NAME);

Employee employee = builder.setName("baeldung")
  .setAddress(addressBuilder)
  .build();
特に、FreeBuilderは、**** __ * Employee *:__内の**既存の__Address __objectをカスタマイズするメソッドも追加します。
Employee employee = builder.setName("baeldung")
  .setAddress(addressBuilder)
  .mutateAddress(a -> a.setPinCode(112200))
  .build();
FreeBuilderでは、__FreeBuilder __typesの他に、https://www.baeldung.com/google-protocol-buffer [protos]などの他のビルダーをネストすることもできます。

[[freebuilder partial]]
=== 10. 部分オブジェクトの構築

前に説明したように、FreeBuilderは、すべての制約違反に対して__IllegalStateException __をスローします。たとえば、必須フィールドの値が欠落しています。
これは*運用環境に望ましい*ことですが、*制約全般に依存しない*単体テストを複雑にします*。
このような制約を緩和するために、FreeBuilderでは部分的なオブジェクトを作成できます。
Employee employee = builder.setName("baeldung")
  .setAge(10)
  .setEmail("[email protected]")
  .buildPartial();

assertNotNull(employee.getEmail());
したがって、_Employee_の必須フィールドをすべて設定していない場合でも、__ email __fieldに有効な値があることを確認できます。

[[freebuilder toString]]
=== 11. カスタムtoString()Method

値オブジェクトでは、*カスタム__toString()__implementationを追加する必要があります。* FreeBuilderでは、__abstract __classesでこれを許可します。
@FreeBuilder
public abstract class Employee {

    abstract String getName();

    abstract int getAge();

    @Override
    public String toString() {
        return getName() + " (" + getAge() + " years old)";
    }

    public static class Builder extends Employee_Builder{
    }
}
__Employee ___をインターフェイスではなく抽象クラスとして宣言し、カスタム__toString()__implementationを提供しました。

[[freebuilder lombok]]
=== 12. 他のビルダーライブラリとの比較

この記事で説明したビルダーの実装は、https://www.baeldung.com/lombok-builder [Lombok]、https://www.baeldung.com/immutables [Immutables]、またはその他のものと非常に似ています。 link:/java-annotation-processing-builder[annotation processor]。 ただし、**既に説明したいくつかの顕著な特徴があります**。
  • **マッパーメソッド

  • ネストされたビルド可能なタイプ

  • 部分オブジェクト

[[freebuilder java]]
=== 13. 結論

この記事では、FreeBuilderライブラリを使用して、Javaでビルダークラスを生成しました。 注釈の助けを借りて、ビルダークラスのさまざまなカスタマイズを実装しました。*このように、実装に必要な定型コードを削減します*。
また、FreeBuilderが他のライブラリのいくつかとどのように異なるかを見て、この記事でそれらの特性のいくつかを簡単に説明しました。
すべてのコード例は、https://github.com/eugenp/tutorials/tree/master/patterns/design-patterns-creational [GitHub]で入手できます。