CatalogReportFactoryで定義されている各抽象メソッドは、関連する一群のファクトリを保持するためのプレースホルダーです。CatalogReportFactoryの各実装は、見出し、明細レコード、および要約セクションを生成できるファクトリの実装を提供するために必要です。リスト6は、HTML形式のレポートを生成するファクトリの実装です(興味があるなら、リスト7のMaterialSorterのコードをご覧ください)。
import domain.*;
public class HtmlCatalogReportFactory extends CatalogReportFactory {
public HtmlCatalogReportFactory(Catalog catalog) {
super(catalog);
}
@Override
protected ReportComponent createDetailRecord(Material material) {
return new HtmlReportDetailRecord(material);
}
@Override
protected ReportComponent createReportHeader() {
return new HtmlReportHeader();
}
@Override
protected ReportComponent createSummary() {
return new HtmlReportSummary();
}
}
import java.util.*;
import domain.*;
public class MaterialSorter implements Comparator<Material> {
private List<Material> materialsCopy;
public MaterialSorter(List<Material> materials) {
materialsCopy = new ArrayList<Material>(materials);
}
@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());
}
public List<Material> sort() {
Collections.sort(materialsCopy, this);
return materialsCopy;
}
}
それぞれの生成メソッドは次に示すReportComponentを返します。
public interface ReportComponent {
void appendTo(Report report);
}
ReportComponentの仕事は、レポート(Report)に付加できるだけの分量のテキストを生成することです。Reportはクライアントのコードを簡素化するための便利な要素がいくつか追加された簡単なヘルパークラス(StringBuilderのラッパー)です。
public class Report {
static final String EOL = System.getProperty("line.separator");
private StringBuilder buffer = new StringBuilder();
@Override
public String toString() {
return buffer.toString();
}
public void appendLine(String text) {
buffer.append(text);
buffer.append(EOL);
}
}
HTMLレポートのコンポーネントを生成する各ファクトリは、大部分がごく簡単な1行のメソッドです。リスト8に、それらのうちの3つの実装とHtmlユーティリティクラスを示します。
// Html.java
public class Html {
public static String td(String element) {
return tag(element, "td");
}
public static String th(String element) {
return tag(element, "th");
}
public static String tag(String element, String tag) {
return String.format("<%s>%s</%s>", tag, element, tag);
}
}
// HtmlReportHeader.java
import static abstractfactory.Html.*;
public class HtmlReportHeader implements ReportComponent {
@Override
public void appendTo(Report report) {
report.appendLine("<body>");
report.appendLine("<table>");
report.appendLine(String.format("<tr>%s%s%s%s</tr>",
th("Title"), th("Auth/Dir"),
th("Classification"), th("Year")));
}
}
// HtmlReportDetailRecord.java
import static abstractfactory.Html.*;
import domain.*;
public class HtmlReportDetailRecord implements ReportComponent {
private final Material material;
public HtmlReportDetailRecord(Material material) {
this.material = material;
}
@Override
public void appendTo(Report report) {
report.appendLine(String.format("<tr>%s%s%s%s</tr>",
td(material.getTitle()), td(material
.getAuthor()), td(material.getClassification()),
td(material.getYear())));
}
}
// HtmlReportSummary.java
public class HtmlReportSummary implements ReportComponent {
@Override
public void appendTo(Report report) {
report.appendLine("</table>");
report.appendLine("</body>");
}
}
ReportComponentのこれらの実装はテストも簡単です! その上、レポートの種類が増えても、きわめて柔軟に対応できます。たとえば、HtmlReportSummaryの実装を使えば、どのようなHTMLレポートも生成できるでしょう。
これはAbstract Factoryパターンの使い方として適切なのでしょうか? また、Builderよりもこちらを使った方がよいのでしょうか? 一概には決められませんが、今回の例に関して言うなら、多分そうです。これは種類に関する情報をクライアントから隠蔽したいという要求を満たしてくれます。今回の例で示したクライアントはレポートが、どこかのプラットフォームなのかそれともWebレポートなのかを示す目印をまだ渡す必要があります。この情報をプロパティのファイルに埋め込んで、Abstract Factoryから直接読み取るようにすることも簡単にできるでしょう。一方、Builderは段階的な構成を強調するように設計されていますが、この例のような目的ではそれは必要ありません。
どのデザインパターンについても言えることですが、Abstract FactoryにはBuilderパターンとよく似た面があります。現実のニーズに合ったパターンを適用することが大切です。多くの場合、複数のパターンを組み合わせて実装することになるでしょう。

