Card 03/ 09
Builder Template in Python, Java and TypeScript
Set one part at a time, then call build() to get a finished, valid object.
The Builder template is the pattern in its pure form, with the participant names from the class diagram: a product with one required part and two optional ones, a builder that refuses a product without the required part, and a director with two fixed configurations. This card builds it one participant at a time in Python, and the tabs then give the complete program in Python, Java and TypeScript.
Building the template one participant at a time
Product
A frozen dataclass, so a product cannot change once it exists.
@dataclass(frozen=True)
class Product:
part_a: str
part_b: Optional[str]
part_c: Optional[str]Builder steps
Each step stores one part and returns the builder, which is what lets calls chain.
def set_part_a(self, value: str) -> "ConcreteBuilder":
self._a = value
return selfbuild()
The one place that checks the parts and creates the product.
def build(self) -> Product:
if self._a is None:
raise ValueError("part A is required")
return Product(self._a, self._b, self._c)Director
Optional: named configurations, written once.
class Director:
def make_minimal(self, builder: Builder) -> Product:
return builder.set_part_a("A").build()The complete template
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Optional
@dataclass(frozen=True)
class Product:
part_a: str
part_b: Optional[str]
part_c: Optional[str]
class Builder(ABC):
@abstractmethod
def set_part_a(self, value: str) -> "Builder": ...
@abstractmethod
def set_part_b(self, value: str) -> "Builder": ...
@abstractmethod
def set_part_c(self, value: str) -> "Builder": ...
@abstractmethod
def build(self) -> Product: ...
class ConcreteBuilder(Builder):
def __init__(self) -> None:
self._a: Optional[str] = None
self._b: Optional[str] = None
self._c: Optional[str] = None
def set_part_a(self, value: str) -> "ConcreteBuilder":
self._a = value
return self
def set_part_b(self, value: str) -> "ConcreteBuilder":
self._b = value
return self
def set_part_c(self, value: str) -> "ConcreteBuilder":
self._c = value
return self
def build(self) -> Product:
if self._a is None:
raise ValueError("part A is required")
return Product(self._a, self._b, self._c)
class Director:
def make_minimal(self, builder: Builder) -> Product:
return builder.set_part_a("A").build()
def make_full(self, builder: Builder) -> Product:
return builder.set_part_a("A").set_part_b("B").set_part_c("C").build()
print(ConcreteBuilder().set_part_a("A").set_part_c("C").build())
print(Director().make_minimal(ConcreteBuilder()))
print(Director().make_full(ConcreteBuilder()))
try:
ConcreteBuilder().set_part_b("B").build()
except ValueError as error:
print("refused:", error)Product(part_a='A', part_b=None, part_c='C')
Product(part_a='A', part_b=None, part_c=None)
Product(part_a='A', part_b='B', part_c='C')
refused: part A is requiredpublic class BdTemplate {
public static void main(String[] args) {
System.out.println(new ConcreteBuilder().setPartA("A").setPartC("C").build());
System.out.println(new Director().makeMinimal(new ConcreteBuilder()));
System.out.println(new Director().makeFull(new ConcreteBuilder()));
try {
new ConcreteBuilder().setPartB("B").build();
} catch (IllegalStateException error) {
System.out.println("refused: " + error.getMessage());
}
}
}
record Product(String partA, String partB, String partC) {}
interface Builder {
Builder setPartA(String value);
Builder setPartB(String value);
Builder setPartC(String value);
Product build();
}
class ConcreteBuilder implements Builder {
private String a;
private String b;
private String c;
public ConcreteBuilder setPartA(String value) { a = value; return this; }
public ConcreteBuilder setPartB(String value) { b = value; return this; }
public ConcreteBuilder setPartC(String value) { c = value; return this; }
public Product build() {
if (a == null) throw new IllegalStateException("part A is required");
return new Product(a, b, c);
}
}
class Director {
Product makeMinimal(Builder builder) {
return builder.setPartA("A").build();
}
Product makeFull(Builder builder) {
return builder.setPartA("A").setPartB("B").setPartC("C").build();
}
}Product[partA=A, partB=null, partC=C]
Product[partA=A, partB=null, partC=null]
Product[partA=A, partB=B, partC=C]
refused: part A is requiredinterface Product {
readonly partA: string;
readonly partB?: string;
readonly partC?: string;
}
interface Builder {
setPartA(value: string): Builder;
setPartB(value: string): Builder;
setPartC(value: string): Builder;
build(): Product;
}
class ConcreteBuilder implements Builder {
private a?: string;
private b?: string;
private c?: string;
setPartA(value: string): this { this.a = value; return this; }
setPartB(value: string): this { this.b = value; return this; }
setPartC(value: string): this { this.c = value; return this; }
build(): Product {
if (this.a === undefined) throw new Error("part A is required");
return Object.freeze({ partA: this.a, partB: this.b, partC: this.c });
}
}
class Director {
makeMinimal(builder: Builder): Product {
return builder.setPartA("A").build();
}
makeFull(builder: Builder): Product {
return builder.setPartA("A").setPartB("B").setPartC("C").build();
}
}
console.log(new ConcreteBuilder().setPartA("A").setPartC("C").build());
console.log(new Director().makeMinimal(new ConcreteBuilder()));
console.log(new Director().makeFull(new ConcreteBuilder()));
try {
new ConcreteBuilder().setPartB("B").build();
} catch (error) {
console.log("refused:", (error as Error).message);
}{ partA: 'A', partB: undefined, partC: 'C' }
{ partA: 'A', partB: undefined, partC: undefined }
{ partA: 'A', partB: 'B', partC: 'C' }
refused: part A is requiredThe first three lines of output are products built three ways: by a chain that skips part B, and by each of the director's configurations. The last is a builder that was given part B alone, and build() refused it. Validation lives in one method, so no product missing part A can exist anywhere in the program.
Returning the builder from every step is not required by the pattern; it is what makes the chained form possible. A builder whose steps return nothing still works, one statement per part.
← → move through sections and cards