0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

本記事は、GoFデザインパターン解説シリーズの1つです。

今回は、生成に関するパターンの中のBuilder(ビルダー)パターンを、Javaのサンプルコードとともに解説します。

解決したい課題

コンストラクタの引数がどんどん増えていく、いわゆる「テレスコーピング・コンストラクタ(望遠鏡状に伸びるコンストラクタ)」という問題があります。

// 引数が多すぎるコンストラクタの例
public class Pizza {
    public Pizza(String size, boolean cheese, boolean pepperoni,
                 boolean mushroom, boolean onion, boolean extraSauce) {
        // ...
    }
}

// 呼び出し側: どの引数が何を表すか、コードを見ただけでは分かりにくい
Pizza pizza = new Pizza("L", true, false, true, false, true);

このようなコンストラクタは、①引数の並び順を間違えやすい、②「一部の項目だけ指定したい」という柔軟な使い方がしづらい、③何番目がどの意味かコードから読み取りにくい、といった問題を抱えます。

Builderパターンは、オブジェクトの組み立て手順を、専用のBuilderオブジェクトに分離し、メソッドチェーンで1つずつ項目を指定してから、最後に組み立てを完了させることで、この問題を解決します。

image.png

クラス構成

  • Product(製品): 最終的に組み立てられる、複雑な構造を持つオブジェクト
  • Builder(ビルダー): Productを組み立てるためのメソッド群を持つクラス。メソッドチェーンで各項目を設定できる
  • Director(ディレクター): (省略されることも多い)決まった手順でBuilderを操作し、定型的なProductを組み立てる役割

Javaでの実装例

先ほどのPizza(ピザ)を例に、Builderパターンで書き直してみます。

public class Pizza {
    private final String size;         // 必須
    private final boolean cheese;
    private final boolean pepperoni;
    private final boolean mushroom;
    private final boolean onion;
    private final boolean extraSauce;

    // コンストラクタはprivateにし、Builder経由でのみ生成させる
    private Pizza(Builder builder) {
        this.size = builder.size;
        this.cheese = builder.cheese;
        this.pepperoni = builder.pepperoni;
        this.mushroom = builder.mushroom;
        this.onion = builder.onion;
        this.extraSauce = builder.extraSauce;
    }

    @Override
    public String toString() {
        return String.format(
            "Pizza[size=%s, cheese=%b, pepperoni=%b, mushroom=%b, onion=%b, extraSauce=%b]",
            size, cheese, pepperoni, mushroom, onion, extraSauce
        );
    }

    // Pizzaクラスの内部クラスとしてBuilderを定義する(一般的な実装パターン)
    public static class Builder {
        // 必須項目
        private final String size;

        // 任意項目(デフォルト値を設定しておく)
        private boolean cheese = false;
        private boolean pepperoni = false;
        private boolean mushroom = false;
        private boolean onion = false;
        private boolean extraSauce = false;

        public Builder(String size) {
            this.size = size;  // 必須項目はBuilderのコンストラクタで受け取る
        }

        // 各メソッドがBuilder自身(this)を返すことで、メソッドチェーンを可能にする
        public Builder cheese(boolean value) {
            this.cheese = value;
            return this;
        }

        public Builder pepperoni(boolean value) {
            this.pepperoni = value;
            return this;
        }

        public Builder mushroom(boolean value) {
            this.mushroom = value;
            return this;
        }

        public Builder onion(boolean value) {
            this.onion = value;
            return this;
        }

        public Builder extraSauce(boolean value) {
            this.extraSauce = value;
            return this;
        }

        // 最後に呼び出し、実際にPizzaインスタンスを組み立てる
        public Pizza build() {
            return new Pizza(this);
        }
    }
}

利用側のコード

public class Main {
    public static void main(String[] args) {
        Pizza pizza = new Pizza.Builder("L")
            .cheese(true)
            .mushroom(true)
            .extraSauce(true)
            .build();

        System.out.println(pizza);

        // 別の組み合わせも、必要な項目だけ指定すればよい
        Pizza simplePizza = new Pizza.Builder("M")
            .cheese(true)
            .build();

        System.out.println(simplePizza);
    }
}

実行結果:

Pizza[size=L, cheese=true, pepperoni=false, mushroom=true, onion=false, extraSauce=true]
Pizza[size=M, cheese=true, pepperoni=false, mushroom=false, onion=false, extraSauce=false]

コンストラクタに大量の引数を並べる代わりに、設定したい項目だけをメソッドチェーンで指定し、最後にbuild()を呼ぶだけで済みます。

コードを読むだけで「どの項目に何を設定しているか」が明確になり、任意項目を省略しても正しく動作します。

image.png

Director(ディレクター)を使う例

決まったパターンの組み立てを何度も行う場合は、その手順自体をDirectorクラスにまとめることもあります。

public class PizzaDirector {
    // 「マルゲリータ風」の決まった組み立て手順をカプセル化する
    public static Pizza createMargheritaStyle(String size) {
        return new Pizza.Builder(size)
            .cheese(true)
            .extraSauce(true)
            .build();
    }
}
Pizza margherita = PizzaDirector.createMargheritaStyle("M");

image.png

Java標準ライブラリでの実例

Builderパターンは、Java標準ライブラリでも使われています。

文字列を組み立てるStringBuilderが代表例です。

String result = new StringBuilder()
    .append("Hello, ")
    .append("World")
    .append("!")
    .toString();

append()が自分自身(StringBuilder)を返すことでメソッドチェーンが可能になっている点は、本記事のPizza例と同じ考え方です。

使用場面

  • コンストラクタの引数(特に任意項目)が多く、可読性が損なわれている場合
  • 「一部の項目だけ指定した、様々なバリエーションのオブジェクト」を作りたい場合
  • オブジェクトの組み立て手順自体が複数のステップに分かれ、順序が重要な場合
  • イミュータブル(不変)なオブジェクトを、柔軟に組み立てて生成したい場合

まとめ

項目 内容
解決する課題 コンストラクタの引数が多すぎて、可読性・柔軟性が損なわれる問題(テレスコーピング・コンストラクタ)
実現方法 組み立て専用のBuilderクラスを用意し、メソッドチェーンで項目を指定してからbuild()で完成させる
メリット 任意項目を自由に組み合わせられ、コードの可読性も高い
実例 Java標準のStringBuilderも同じ考え方を採用している

おすすめ書籍

GoFの23パターンをより深く学びたい方には、以下の書籍がおすすめです。
Java言語のサンプルコードとUMLを使い、初心者にもわかりやすく解説されています。

Java言語で学ぶデザインパターン入門 第3版 [ 結城 浩 ]

※本リンクはアフィリエイトリンクを含みます。

参考

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?