広告

Java : Builder パターン (図解/デザインパターン)

Builder パターンとは、GoF によって定義されたデザインパターンの1つです。

複雑なオブジェクト生成処理をシンプルにします。
また、同じ手続きで異なる型のオブジェクトを生成できます。

本記事では、Builder パターンを Java のコード付きで解説していきます。


デザインパターン(GoF) 関連記事

概要

Builder パターン(ビルダー・パターン)とは、GoF(Gang of Four; 4人のギャングたち)によって定義されたデザインパターンの1つである。 オブジェクトの生成過程を抽象化することによって、動的なオブジェクトの生成を可能にする。

Builder パターンとは、

  • 複雑なオブジェクト生成処理をシンプルにする
  • 同じ手続きで異なる型のオブジェクトを生成する

という2つの目的をもった設計です。

Builder は、日本語的に発音すると「ビルダー」となります。
意味は「建築者」ですね。


【Builder パターンのイメージ】

イメージ図1

複雑なオブジェクト生成処理 …
例えば、コンストラクタにパラメータがたくさん必要となる「製品」クラスを考えてみましょう。

class 製品 {
    コンストラクタ(パラメータ1, パラメータ2, パラメータ3, ... , パラメータn) {
        ...
    }
}

コンストラクタに指定するパラメータがたくさんあると、

  • あのパラメータは何番目だっけ…?
  • どのパラメータが省略可能だったかな…?

といったことを気にしながらコードを書くことになります。
これはコーディングの効率を下げる要因にもなるでしょう。

「ビルダー」クラスの役割は、このような複雑なオブジェクト生成処理をシンプルにすることです。


イメージ図2

そして、もう1つ。

「ディレクター」クラスは、オブジェクトの構築手順を定義します。
毎回、同じパラメータを持つオブジェクトを生成するのに便利です。

また「ビルダー」を切り替えることで、継承関係のない 異なる型 のオブジェクトを生成できます。


【補足】

GoF による Builder パターン は、

  • 複雑なオブジェクト生成処理をシンプルにする
  • 同じ手続きで異なる型のオブジェクトを生成する

という2つの目的があります。

もし

  • 複雑なオブジェクト生成処理をシンプルにする

という目的だけであれば、代わりに「簡易的な Builder パターン」が使えるかもしれません。
そちらも検討してみることをおすすめします。

(個人的には、GoF による Builder パターンは少し大げさというか、オーバースペックにも感じます。
 もちろん、このパターンが適した場面もあるとは思います)

関連記事:


クラス図とシーケンス図

クラス図1

シーケンス図1

上の図は、Builder パターンの一般的なクラス図とシーケンス図です。
それでは、このクラス図をそのままコードにしてみましょう。

まずはクラス図の下のほうにある Product クラスです。
ProductA と ProductB は似ているクラスですが、継承関係はありません。

クラスフィールドに part1part2 変数を持ち、toString メソッドでその内容を返すだけです。

public class ProductA {
    private final String part1;
    private final String part2;

    public ProductA(String part1, String part2) {
        this.part1 = part1;
        this.part2 = part2;
    }

    @Override
    public String toString() {
        return """
                ProductA :
                  part1 = %s
                  part2 = %s
                """
                .formatted(part1, part2);
    }
}

public class ProductB {
    private final String part1;
    private final String part2;

    public ProductB(String part1, String part2) {
        this.part1 = part1;
        this.part2 = part2;
    }

    @Override
    public String toString() {
        return """
                ProductB :
                  part1 = %s
                  part2 = %s
                """
                .formatted(part1, part2);
    }
}

次に、Builder インタフェースとその実装クラスです。

  1. reset メソッド : パラメータを初期化
  2. buildPart メソッド : パラメータを設定
  3. getResult メソッド : Product オブジェクトを生成
public interface Builder {
    void reset();

    void buildPart1(String part);

    void buildPart2(String part);
}

public class ConcreteBuilderA implements Builder {
    private String part1;
    private String part2;

    @Override
    public void reset() {
        part1 = "未設定";
        part2 = "未設定";
    }

    @Override
    public void buildPart1(String part) {
        part1 = part;
    }

    @Override
    public void buildPart2(String part) {
        part2 = part;
    }

    public ProductA getResult() {
        return new ProductA(part1, part2);
    }
}

public class ConcreteBuilderB implements Builder {
    private String part1;
    private String part2;

    @Override
    public void reset() {
        part1 = "未設定";
        part2 = "未設定";
    }

    @Override
    public void buildPart1(String part) {
        part1 = part;
    }

    @Override
    public void buildPart2(String part) {
        part2 = part;
    }

    public ProductB getResult() {
        return new ProductB(part1, part2);
    }
}

最後に Director クラスです。
Builder を使って Product オブジェクトを構築します。

public class Director {
    private final Builder builder;

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

    public void construct1() {
        builder.reset();
        builder.buildPart1("abc");
        builder.buildPart2("123");
    }

    public void construct2() {
        builder.reset();
        builder.buildPart1("XYZ");
    }
}

それでは、これらのクラスを使ってみましょう。

はじめに ConcreteBuilderA クラスを使う例です。
ConcreteBuilderA は ProductA オブジェクトを生成します。

final var builder = new ConcreteBuilderA();
final var director = new Director(builder);

director.construct1();

final var product1 = builder.getResult();
System.out.print(product1);

// 結果
// ↓
//ProductA :
//  part1 = abc
//  part2 = 123

director.construct2();

final var product2 = builder.getResult();
System.out.print(product2);

// 結果
// ↓
//ProductA :
//  part1 = XYZ
//  part2 = 未設定

次は ConcreteBuilderB クラスを使います。
ConcreteBuilderB は ProductB オブジェクトを生成します。

final var builder = new ConcreteBuilderB();
final var director = new Director(builder);

director.construct1();

final var product1 = builder.getResult();
System.out.print(product1);

// 結果
// ↓
//ProductB :
//  part1 = abc
//  part2 = 123

director.construct2();

final var product2 = builder.getResult();
System.out.print(product2);

// 結果
// ↓
//ProductB :
//  part1 = XYZ
//  part2 = 未設定

結果も問題なしですね。


具体的な例

もう少し具体的な例も見てみましょう。

クラス図2

Book と WebPage クラスのオブジェクトを生成する Builder パターンを考えてみます。

Book クラスは、次のパラメータを持ちます。

  • タイトル (Title)
  • 著者 (Author)
  • ページ数 (totalPages)
public class Book {
    private final String title;
    private final String author;
    private final int totalPages;

    public Book(String title, String author, int totalPages) {
        this.title = title;
        this.author = author;
        this.totalPages = totalPages;
    }

    @Override
    public String toString() {
        return """
                ---- Book ----
                タイトル : %s
                著者   : %s
                ページ数 : %d
                """.formatted(title, author, totalPages);
    }
}

WebPage クラスは、次のパラメータを持ちます。

  • タイトル
  • 著者

(基本1ページで構成することにしたので「ページ数」は持ちません)

public class WebPage {
    private final String title;
    private final String author;

    public WebPage(String title, String author) {
        this.title = title;
        this.author = author;
    }

    @Override
    public String toString() {
        return """
                ---- WebPage ----
                タイトル : %s
                著者   : %s
                """.formatted(title, author);
    }
}

Builder インタフェースは、Book と WebPage オブジェクトの生成に必要となる

  • タイトル
  • 著者
  • ページ数

を設定するメソッドを持ちます。

public interface Builder {
    void reset();

    void setTitle(String title);

    void setAuthor(String author);

    void setTotalPages(int totalPages);
}

BookBuilder クラスは、Book オブジェクトを生成します。
reset メソッドでは、それぞれのパラメータのデフォルト値で初期化します。

public class BookBuilder implements Builder {
    private String title;
    private String author;
    private int totalPages;

    @Override
    public void reset() {
        title = "なし";
        author = "不明";
        totalPages = -1;
    }

    @Override
    public void setTitle(String title) {
        this.title = title;
    }

    @Override
    public void setAuthor(String author) {
        this.author = author;
    }

    @Override
    public void setTotalPages(int totalPages) {
        this.totalPages = totalPages;
    }

    public Book getResult() {
        return new Book(title, author, totalPages);
    }
}

WebBuilder クラスは、WebPage オブジェクトを生成します。
WebPage は「ページ数」を持たないので、setTotalPages メソッドはなにもしません。

public class WebPageBuilder implements Builder {
    private String title;
    private String author;

    @Override
    public void reset() {
        title = "なし";
        author = "不明";
    }

    @Override
    public void setTitle(String title) {
        this.title = title;
    }

    @Override
    public void setAuthor(String author) {
        this.author = author;
    }

    @Override
    public void setTotalPages(int totalPages) {
        // なにもしません。
    }

    public WebPage getResult() {
        return new WebPage(title, author);
    }
}

最後に Director クラスです。
Builder を使い、決まった手順 でオブジェクトを構築します。

public class Director {
    private final Builder builder;

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

    public void constructDictionary() {
        builder.reset();
        builder.setTitle("いろいろ辞典");
        builder.setAuthor("アリス");
        builder.setTotalPages(1000);
    }

    public void constructFairyTale() {
        builder.reset();
        builder.setTitle("いろいろ童話");
        builder.setTotalPages(200);
    }
}

それでは、これらのクラスを使ってみましょう。
まずは BookBuilder を使って Book オブジェクトを生成します。

final var builder = new BookBuilder();
final var director = new Director(builder);

director.constructDictionary();

final var book1 = builder.getResult();
System.out.print(book1);

// 結果
// ↓
//---- Book ----
//タイトル : いろいろ辞典
//著者   : アリス
//ページ数 : 1000

director.constructFairyTale();

final var book2 = builder.getResult();
System.out.print(book2);

// 結果
// ↓
//---- Book ----
//タイトル : いろいろ童話
//著者   : 不明
//ページ数 : 200

次は WebPageBuilder を使って WebPage オブジェクトを生成します。

final var builder = new WebPageBuilder();
final var director = new Director(builder);

director.constructDictionary();

final var webPage1 = builder.getResult();
System.out.print(webPage1);

// 結果
// ↓
//---- WebPage ----
//タイトル : いろいろ辞典
//著者   : アリス

director.constructFairyTale();

final var webPage2 = builder.getResult();
System.out.print(webPage2);

// 結果
// ↓
//---- WebPage ----
//タイトル : いろいろ童話
//著者   : 不明

結果も問題なしですね。
ビルダーを切り替えることで、生成するオブジェクトのも切り替えることができました。

まとめ

Builder パターンとは、

  • 複雑なオブジェクト生成処理をシンプルにする
  • 同じ手続きで異なる型のオブジェクトを生成する

という2つの目的をもった設計です。

もし

  • 複雑なオブジェクト生成処理をシンプルにする

という目的だけであれば、代わりに「簡易的な Builder パターン」が使えるかもしれません。

よく検討してから使いたいですね。


関連記事

ページの先頭へ