Lab
Builder
type Stop = { place: string; nights: number };
type TripPlan = { name: string; kind: string; stops: Stop[]; nights: number };
// The builder owns the draft: setters replace, addStop appends.
class TripBuilder {
private tripName = '';
private tripKind = 'day-trip';
private stops: Stop[] = [];
name(value: string): this {
this.tripName = value;
return this;
}
kind(value: string): this {
this.tripKind = value;
return this;
}
addStop(stop: Stop): this {
this.stops.push({ ...stop });
return this;
}
// build checks the whole draft, then returns the plan.
build(): TripPlan {
const nights = this.stops.reduce((total, stop) => total + stop.nights, 0);
if (this.tripKind === 'day-trip' && nights > 0)
throw new Error('a day trip cannot include an overnight stay');
if (this.tripKind === 'overnight' && nights === 0)
throw new Error('an overnight trip needs at least one night');
const stops = this.stops.map((stop) => ({ ...stop }));
return { name: this.tripName, kind: this.tripKind, stops, nights };
}
}
const show = (plan: TripPlan) => {
const route = plan.stops.map((stop) => stop.place).join(' → ') || 'no stops';
return `${plan.name}, ${plan.kind}: ${route}, nights=${plan.nights}`;
};
const builder = new TripBuilder().name('Weekend route');
builder.addStop({ place: 'Harbor', nights: 0 });
const first = builder.build();
builder.addStop({ place: 'Old town', nights: 1 });
let blocked = '';
try {
builder.build();
} catch (error) {
blocked = (error as Error).message;
}
builder.kind('overnight');
const second = builder.build();
console.log(`first: ${show(first)}`);
console.log(`blocked: ${blocked}`);
console.log(`second: ${show(second)}`);
Builder’s draft mutable
Waiting for the draft
kind—
Stops appear when recorded
build · validate + hand off
firstTripPlan
Not observed yet
secondTripPlan
Not observed yet
Step to observe the first assembly call.