IntelliJでクラスのJavaビルダーを作成する

1. 前書き

link:/creational-design-patterns#builder[Builderデザインパターン]は、最も広く使用されている作成パターンの1つです。 複雑なオブジェクトを構築するのに役立ちます。
*手作業でビルダーを作成するのは面倒でエラーが発生しやすいです。 したがって、可能な場合は専用ツールを使用して自動生成する必要があります。*
このチュートリアルでは、https://www.jetbrains.com/idea/ [IntelliJ] IDEでビルダークラスを自動的に作成するさまざまな方法を検討します。 IntelliJがすぐに提供する組み込み機能と、サードパーティのプラグインを見ていきます。

2. 初期設定

この記事では、IntelliJ IDEA Communityエディションのバージョン2019.1.3を使用します。これは、執筆時点での最新リリースです。 *ただし、例に示されているすべての手法は、IDEAの他のバージョンでも正常に機能するはずです。*
ビルダーを生成する_Book_クラスの定義から始めましょう。
public class Book {
    private String title;
    private Author author;
    private LocalDate publishDate;
    private int pageCount;

    // standard constructor(s), getters and setters
}

3. IntelliJの組み込み機能を使用する

  • IntelliJの組み込みツールを使用して_Book_クラスのビルダーを生成するには、適切なコンストラクターが必要です。*

    作成しましょう:
public Book(String title, Author author, LocalDate publishDate, int pageCount) {
    this.title = title;
    this.author = author;
    this.publishDate = publishDate;
    this.pageCount = pageCount;
}
これで、ビルダーを作成する準備が整いました。 したがって、作成したコンストラクターにカーソルを置き、_Ctrl + Alt + Shift + T_を押してhttps://www.jetbrains.com/help/idea/refactoring-source-code.html#refactoring_invoke[_Refactor This_]ポップアップを開きます。 (PC上)およびhttps://www.jetbrains.com/help/idea/replace-constructor-with-builder.html[_Replace Constructor with Builder_]リファクタリングを選択します。
link:/uploads/Screenshot-2019-07-27-at-20.51.48.png%201506w []
ビルダークラスのオプション(名前やターゲットパッケージなど)をさらに調整できます。
link:/uploads/Screenshot-2019-07-27-at-20.53.03.png%201134w []
その結果、_BookBuilder_クラスを生成しました。
public class BookBuilder {
    private String title;
    private Author author;
    private LocalDate publishDate;
    private int pageCount;

    public BookBuilder setTitle(String title) {
        this.title = title;
        return this;
    }

    public BookBuilder setAuthor(Author author) {
        this.author = author;
        return this;
    }

    public BookBuilder setPublishDate(LocalDate publishDate) {
        this.publishDate = publishDate;
        return this;
    }

    public BookBuilder setPageCount(int pageCount) {
        this.pageCount = pageCount;
        return this;
    }

    public Book createBook() {
        return new Book(title, author, publishDate, pageCount);
    }
}

3.1. カスタムセッタープレフィックス

ビルダークラスのセッターメソッドに_with_ prefixを使用するのが一般的な方法です。
*デフォルトのプレフィックスを変更するには、オプションウィンドウの右上隅にある_Rename Setters Prefix_アイコンを選択する必要があります*:
link:/uploads/Screenshot-2019-07-27-at-20.54.03.png%201110w []

3.2. 静的内部ビルダー

  • Joshua Bloch by Effective Javaのように、静的な内部クラスとしてビルダーを実装することを好む人もいます。

    この場合、IntelliJの_Replace Constructor with Builder_機能を使用してこれを達成するために、いくつかの追加手順を実行する必要があります。
    まず、空の内部クラスを手動で作成し、コンストラクターをプライベートにする必要があります。
public class Book {

    private String title;
    private Author author;
    private LocalDate publishDate;
    private int pageCount;

    public static class Builder {

    }

    private Book(String title, Author author, LocalDate publishDate, int pageCount) {
        this.title = title;
        this.author = author;
        this.publishDate = publishDate;
        this.pageCount = pageCount;
    }

    // standard getters and setters
}
さらに、オプションウィンドウで[既存を使用]を選択し、新しく作成したクラスをポイントする必要があります。
link:/uploads/Screenshot-2019-07-27-at-20.55.02.png%201116w []
 

4. InnerBuilderプラグインの使用

*では、https://plugins.jetbrains.com/plugin/7354-innerbuilder [InnerBuilder]プラグインを使用して、_Book_クラスのビルダーを生成する方法を見てみましょう。*
プラグインをインストールしたら、_Alt + Insert_(PC上)を押して_Builderを選択することにより、https://www.jetbrains.com/help/idea/generated-code.html [_Generate_]ポップアップを開くことができます。 …_オプション:
link:/uploads/Screenshot-2019-07-27-at-20.56.07.png%201546w []
または、_Alt + Shift + B_(PC)を押して、InnerBuilderプラグインを直接呼び出すこともできます。
link:/uploads/Screenshot-2019-07-27-at-20.56.14.png%201130w []
ご覧のとおり、生成されたビルダーをカスタマイズするために選択できるオプションがいくつかあります。
すべてのオプションがオフになっているときに生成されたビルダーを見てみましょう。
public static final class Builder {
    private String title;
    private Author author;
    private LocalDate publishDate;
    private int pageCount;

    public Builder() {
    }

    public Builder title(String val) {
        title = val;
        return this;
    }

    public Builder author(Author val) {
        author = val;
        return this;
    }

    public Builder publishDate(LocalDate val) {
        publishDate = val;
        return this;
    }

    public Builder pageCount(int val) {
        pageCount = val;
        return this;
    }

    public Book build() {
        return new Book(this);
    }
}
  • InnerBuilderプラグインは、デフォルトでビルダーを静的内部クラスとして実装します。*

5. Builder Generatorプラグインの使用

最後に、https://plugins.jetbrains.com/plugin/6585-builder-generator [Builder Generator]の仕組みを見てみましょう。
同様に、InnerBuilderについては、_Alt + Insert_(PC上)を押して_Builder_オプションを選択するか、_Alt + Shift + B_ショートカットを使用できます。
link:/uploads/Screenshot-2019-07-27-at-20.57.51.png%201540w []
ご覧のとおり、_BookBuilder_をカスタマイズするために選択できる3つのオプションがあります。
link:/uploads/Screenshot-2019-07-27-at-20.57.56-100x63.png%20100w []
すべてのオプションをオフのままにして、生成されたビルダークラスを見てみましょう。
public final class BookBuilder {
    private String title;
    private Author author;
    private LocalDate publishDate;
    private int pageCount;

    private BookBuilder() {
    }

    public static BookBuilder aBook() {
        return new BookBuilder();
    }

    public BookBuilder withTitle(String title) {
        this.title = title;
        return this;
    }

    public BookBuilder withAuthor(Author author) {
        this.author = author;
        return this;
    }

    public BookBuilder withPublishDate(LocalDate publishDate) {
        this.publishDate = publishDate;
        return this;
    }

    public BookBuilder withPageCount(int pageCount) {
        this.pageCount = pageCount;
        return this;
    }

    public Book build() {
        Book book = new Book();
        book.setTitle(title);
        book.setAuthor(author);
        book.setPublishDate(publishDate);
        book.setPageCount(pageCount);
        return book;
    }
}
作成されたビルダークラスをカスタマイズするためにBuilder Generatorプラグインが提供する最初のオプション_Inner builder –_は、一目瞭然です。
*ただし、他の2つはより興味深いものであり、次のセクションでそれらを検討します。*

5.1. 「but」メソッドオプション

このオプションを選択すると、プラグインは_but()_メソッドを_BookBuilder_クラスに追加します。
public BookBuilder but() {
    return aBook().withTitle(title).withAuthor(author)
      .withPublishDate(publishDate).withPageCount(pageCount);
}
ここで、著者とページ数が同じで、タイトルと発行日が異なる3冊の本を作成するとします。 *共通のプロパティがすでに設定されているベースビルダーを作成し、_but()_メソッドを使用して、そこから新しい__BookBuilder__s(および後で__Book__s)を作成します。*
例を見てみましょう:
BookBuilder commonBuilder = BookBuilder.aBook().withAuthor(johnDoe).withPageCount(123);

Book my_first_book = commonBuilder.but()
  .withPublishDate(LocalDate.of(2017, 12, 1))
  .withTitle("My First Book").build();

Book my_second_book = commonBuilder.but()
  .withPublishDate(LocalDate.of(2018, 12, 1))
  .withTitle("My Second Book").build();

Book my_last_book = commonBuilder.but()
  .withPublishDate(LocalDate.of(2019, 12, 1))
  .withTitle("My Last Book").build();

5.2. 単一のフィールドオプションを使用する

このオプションを選択すると、生成されたビルダーは、ブックのすべてのプロパティではなく、作成された_Book_オブジェクトへの参照を保持します。
public final class BookBuilder {
    private Book book;

    private BookBuilder() {
        book = new Book();
    }

    public static BookBuilder aBook() {
        return new BookBuilder();
    }

    public BookBuilder withTitle(String title) {
        book.setTitle(title);
        return this;
    }

    public BookBuilder withAuthor(Author author) {
        book.setAuthor(author);
        return this;
    }

    public BookBuilder withPublishDate(LocalDate publishDate) {
        book.setPublishDate(publishDate);
        return this;
    }

    public BookBuilder withPageCount(int pageCount) {
        book.setPageCount(pageCount);
        return this;
    }

    public Book build() {
        return book;
    }
}
*これは、特定の状況で役立つビルダークラスを作成するための少し異なるアプローチです。*

6. 結論

このチュートリアルでは、IntelliJでビルダークラスを生成するさまざまな方法を検討しました。
*通常、これらの種類のツールを使用して、ビルダーを自動的に生成することをお勧めします*。 提示した各オプションにはそれぞれ長所と短所があります。 実際にどのアプローチを選択するかは、好みと個人の好みの問題です。