Java : Builder パターン (図解/デザインパターン)
Builder パターンとは、GoF によって定義されたデザインパターンの1つです。
複雑なオブジェクト生成処理をシンプルにします。
また、同じ手続きで異なる型のオブジェクトを生成できます。
本記事では、Builder パターンを Java のコード付きで解説していきます。
デザインパターン(GoF) 関連記事
- 生成に関するパターン
- 振る舞いに関するパターン
概要
Builder パターンとは、
- 複雑なオブジェクト生成処理をシンプルにする
- 同じ手続きで異なる型のオブジェクトを生成する
という2つの目的をもった設計です。
Builder は、日本語的に発音すると「ビルダー」となります。
意味は「建築者」ですね。
【Builder パターンのイメージ】
複雑なオブジェクト生成処理 …
例えば、コンストラクタにパラメータがたくさん必要となる「製品」クラスを考えてみましょう。
class 製品 {
コンストラクタ(パラメータ1, パラメータ2, パラメータ3, ... , パラメータn) {
...
}
}
コンストラクタに指定するパラメータがたくさんあると、
- あのパラメータは何番目だっけ…?
- どのパラメータが省略可能だったかな…?
といったことを気にしながらコードを書くことになります。
これはコーディングの効率を下げる要因にもなるでしょう。
「ビルダー」クラスの役割は、このような複雑なオブジェクト生成処理をシンプルにすることです。
そして、もう1つ。
「ディレクター」クラスは、オブジェクトの構築手順を定義します。
毎回、同じパラメータを持つオブジェクトを生成するのに便利です。
また「ビルダー」を切り替えることで、継承関係のない 異なる型 のオブジェクトを生成できます。
【補足】
GoF による Builder パターン は、
- 複雑なオブジェクト生成処理をシンプルにする
- 同じ手続きで異なる型のオブジェクトを生成する
という2つの目的があります。
もし
- 複雑なオブジェクト生成処理をシンプルにする
という目的だけであれば、代わりに「簡易的な Builder パターン」が使えるかもしれません。
そちらも検討してみることをおすすめします。
(個人的には、GoF による Builder パターンは少し大げさというか、オーバースペックにも感じます。
もちろん、このパターンが適した場面もあるとは思います)
関連記事:
クラス図とシーケンス図
上の図は、Builder パターンの一般的なクラス図とシーケンス図です。
それでは、このクラス図をそのままコードにしてみましょう。
まずはクラス図の下のほうにある Product クラスです。
ProductA と ProductB は似ているクラスですが、継承関係はありません。
クラスフィールドに part1 と part2 変数を持ち、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 インタフェースとその実装クラスです。
- reset メソッド : パラメータを初期化
- buildPart メソッド : パラメータを設定
- 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 = 未設定
結果も問題なしですね。
具体的な例
もう少し具体的な例も見てみましょう。
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 パターン」が使えるかもしれません。
よく検討してから使いたいですね。
関連記事
- 標準APIにならう命名規則
- コメントが少なくて済むコードを目指そう
- シングルトン・パターンの乱用はやめよう
- メソッドのパラメータ(引数)は使う側でチェックしよう
- 不変オブジェクト(イミュータブル) とは
- 依存性の注入(DI)をもっと気軽に
- 不要になったコードはコメントアウトで残さずに削除しよう
- 簡易的な Builder パターン
- 読み取り専用(const) のインタフェースを作る
- 図解/デザインパターン一覧 (GoF)
- Abstract Factory パターン
- Adapter パターン
- Bridge パターン
- Chain of Responsibility パターン
- Command パターン
- Composite パターン
- Decorator パターン
- Facade パターン
- Factory Method パターン
- Flyweight パターン
- Interpreter パターン
- Iterator パターン
- Mediator パターン
- Memento パターン
- Observer パターン
- Prototype パターン
- Proxy パターン
- Singleton パターン
- State パターン
- Strategy パターン
- Template Method パターン
- Visitor パターン