+TypeScript example
ReportRecipe owns the order of title and row steps; each builder turns those steps into text or HTML. Changing the recipe and changing a representation therefore affect different code.
// Declare common steps so each format does not repeat the construction recipe.
interface IReportBuilder {
title(value: string): void;
row(value: string): void;
finish(): string;
}
class TextBuilder implements IReportBuilder {
private readonly parts: string[] = [];
title(value: string): void { this.parts.push(value); }
row(value: string): void { this.parts.push("- " + value); }
finish(): string { return this.parts.join("\n"); }
}
function htmlText(value: string): string {
return value.replaceAll("&", "&").replaceAll("<", "<")
.replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
}
class HtmlBuilder implements IReportBuilder {
private readonly parts: string[] = [];
title(value: string): void { this.parts.push("<h1>" + htmlText(value) + "</h1>"); }
row(value: string): void { this.parts.push("<p>" + htmlText(value) + "</p>"); }
finish(): string { return this.parts.join(""); }
}
// Own the sequence here and delegate the representation to the supplied builder.
class ReportRecipe {
static build(builder: IReportBuilder): string {
builder.title("Report");
builder.row("A < B");
return builder.finish();
}
}
console.log(ReportRecipe.build(new TextBuilder()));
console.log(ReportRecipe.build(new HtmlBuilder()));
Report
- A < B
<h1>Report</h1><p>A < B</p>
The HTML builder encodes values inserted as text. Create a builder for each report; finish() reads its state without resetting it.