Builderパターンの例
図書館の所蔵目録(カタログ)は、書籍や映画などさまざまな資料の集合体です(リスト1を参照)。図書館ではいろいろなレポートを頻繁に印字しますが、まず考えられるのは、館内のすべての資料を一覧にして種類ごとにソートした所蔵資料レポートです(きっと相当に膨大なレポートになるでしょう)。
// Material.java: public class Material { private String classification; private String title; private String director; private String year; public Material(String classification, String title, String director, String year) { this.classification = classification; this.title = title; this.director = director; this.year = year; } public String getAuthor() { return director; } public String getClassification() { return classification; } public String getTitle() { return title; } public String getYear() { return year; } } // Book.java: public class Book extends Material { public Book(String classification, String title, String author, String year) { super(classification, title, author, year); } // ... } // Movie.java: public class Movie extends Material { public Movie(String classification, String title, String director, String year) { super(classification, title, director, year); } public String getDirector() { return getAuthor(); } // ... }
Catalogクラスは、各種資料のシンプルなコンテナです。リスト2をご覧ください。
package builder; import java.util.*; public class Catalog implements Iterable<Material> { private List<Material> materials = new ArrayList<Material>(); public void add(Material material) { materials.add(material); } public List<Material> materials() { return materials; } @Override public Iterator<Material> iterator() { return materials.iterator(); } }
レポートはシンプルな生成プロセスを使用して作られます。はじめに、資料の一覧を種類ごとにグループ化して(たとえば、最初に書籍、次に映画のように)ソートします。レポートには適切なヘッダーを印字します。次に、ソートした所蔵資料の一覧全体について繰り返し処理を行い、各行を出力していきます。最後に、レポートの末尾に適切な補足情報を印字します。
リスト生成の各ステップの詳細は状況により変わってきます。たとえば、Webレポートでは適切なHTMLタグが必要です。あるいは、横幅に制約のあるプリントアウト(明細書など)では、おそらくいくつかのフィールドの割愛や、アウトプットの再レイアウトが必要でしょう。
Builderパターンは、こうしたレポート作成機能の設計を取りまとめるための理想的な手段を提供します。Builderパターンでは、クライアントがオブジェクトの種類と内容を指定するという方法で複雑なオブジェクトの生成を制御します。このクライアントは「ディレクター(director)」と呼ばれ、実装の詳細を一切指定しません。実装の詳細は別のビルダー階層の担当になります。
リスト3に、ディレクタークラスを示します。ディレクターオブジェクトの生成アルゴリズムは、generate
メソッドの1行として表現されます。つまり、まずCatalogオブジェクトをsort
関数でソートし、それを適切なビルダーオブジェクトと共にcreateReport
メソッドに引き渡します。createReport
内のコードでは、ビルダーオブジェクトへのコールバックを何度か行います。具体的には、builderに対してまずヘッダーの生成を要求し、次に各資料に対応するオブジェクトの詳細なアウトプットの生成を要求し、最後にフッターの生成を要求します。このcreateReport
メソッドが生成アルゴリズムの「シェル」に該当します。
最終的には、ビルダーがgetReport
メソッドによってレポートを出力します。ディレクターは、このレポートを呼び出し側のクライアントへ返します。
import java.util.*; public class CatalogReportDirector { private final Catalog catalog; public CatalogReportDirector(Catalog catalog) { this.catalog = catalog; } public String generate(CatalogReportBuilder builder) { return createReport(builder, sort(catalog)); } private List<Material> sort(Catalog catalog) { List<Material> catalogCopy = new ArrayList<Material>(catalog.materials()); Collections.sort(catalogCopy, new Comparator<Material>() { @Override public int compare(Material material1, Material material2) { if (material1.getClass() == material2.getClass()) { if (material1.getTitle(). equals(material2.getTitle())) return material1.getAuthor(). compareTo(material2.getAuthor()); return material1.getTitle(). compareTo(material2.getTitle()); } return material1.getClass().getName().compareTo( material2.getClass().getName()); } }); return catalogCopy; } private String createReport(CatalogReportBuilder builder, List<Material> sortedCatalog) { builder.generateHeader(); for (Material material: sortedCatalog) builder.generateDetail(material); builder.generateSummary(); return builder.getReport(); } }