Creational Patterns • Builder PatternMedium⏱️ ~2 min
Builder Pattern: Parking Lot Ticket Application
Let's apply Builder to design a
ParkingTicket in a parking lot system. Tickets have many attributes: some required (ticket ID, vehicle plate, entry time), some optional (assigned spot, parking attendant, special permit, discount code).ParkingTicket
- ticketId: String
- vehiclePlate: String
- entryTime: DateTime
- assignedSpot: ParkingSpot
- attendantId: String
- specialPermit: Permit
- discountCode: String
- vehiclePlate: String
- entryTime: DateTime
- assignedSpot: ParkingSpot
- attendantId: String
- specialPermit: Permit
- discountCode: String
- ParkingTicket(builder)
+ getTicketId(): String
+ calculateFee(exitTime): Money
+ getAssignedSpot(): ParkingSpot
+ getTicketId(): String
+ calculateFee(exitTime): Money
+ getAssignedSpot(): ParkingSpot
▲
ParkingTicket.Builder
- ticketId: String
- vehiclePlate: String
- entryTime: DateTime
- assignedSpot: ParkingSpot
- attendantId: String
- specialPermit: Permit
- discountCode: String
- vehiclePlate: String
- entryTime: DateTime
- assignedSpot: ParkingSpot
- attendantId: String
- specialPermit: Permit
- discountCode: String
+ Builder(id, plate, time)
+ assignSpot(spot): Builder
+ setAttendant(id): Builder
+ applyPermit(permit): Builder
+ applyDiscount(code): Builder
+ build(): ParkingTicket
+ assignSpot(spot): Builder
+ setAttendant(id): Builder
+ applyPermit(permit): Builder
+ applyDiscount(code): Builder
+ build(): ParkingTicket
Design Walkthrough:
Required Parameters in Constructor:
The builder constructor enforces
Optional Parameters via Fluent Methods:
Not all vehicles get assigned spots immediately (valet parking might assign later). Not all entries are handled by attendants (automated entry gates). Special permits and discounts are rare cases. Each optional attribute has a setter method returning
Validation in build():
The
Required Parameters in Constructor:
The builder constructor enforces
ticketId, vehiclePlate, and entryTime because every ticket must have these. This prevents the system from creating invalid tickets.Optional Parameters via Fluent Methods:
Not all vehicles get assigned spots immediately (valet parking might assign later). Not all entries are handled by attendants (automated entry gates). Special permits and discounts are rare cases. Each optional attribute has a setter method returning
this.Validation in build():
The
build() method performs cross-field validation. For example, if a discountCode is applied, verify it is valid for the vehicle type. If a specialPermit is set, ensure the assigned spot allows permit parking. Only after validation passes does build() call the private ParkingTicket constructor.Usage Example:
ticket = ParkingTicket.Builder("T12345", "ABC-1234", currentTime)
.assignSpot(spotA5)
.setAttendant("ATD-007")
.applyPermit(disabledPermit)
.build()
// Minimal ticket for automated entry
autoTicket = ParkingTicket.Builder("T12346", "XYZ-9876", currentTime)
.build()
ticket = ParkingTicket.Builder("T12345", "ABC-1234", currentTime)
.assignSpot(spotA5)
.setAttendant("ATD-007")
.applyPermit(disabledPermit)
.build()
// Minimal ticket for automated entry
autoTicket = ParkingTicket.Builder("T12346", "XYZ-9876", currentTime)
.build()
Why Builder Fits Here:
First, Tickets have clear required vs optional distinction. Entry cannot happen without ID, plate, and time, but spot assignment can be deferred.
Second, Validation depends on combinations. Discount eligibility might depend on permit type or vehicle category, which is complex to validate in a constructor.
Third, Immutability is critical. Once issued, a ticket's core attributes should not change (though you might add an exit time later through a separate method, but builder-set fields stay fixed).
Fourth, Readability matters in domain models.
First, Tickets have clear required vs optional distinction. Entry cannot happen without ID, plate, and time, but spot assignment can be deferred.
Second, Validation depends on combinations. Discount eligibility might depend on permit type or vehicle category, which is complex to validate in a constructor.
Third, Immutability is critical. Once issued, a ticket's core attributes should not change (though you might add an exit time later through a separate method, but builder-set fields stay fixed).
Fourth, Readability matters in domain models.
applyPermit(permit) is far clearer than setPermit(permit) or a constructor with eight parameters.Interview Tip: When asked to design a ticket system, explicitly mention: "I will use Builder because tickets have required fields (ID, plate, entry time) and optional fields (spot, attendant, permit). This avoids telescoping constructors and ensures immutability." Then draw the class diagram showing the nested builder.
💡 Key Takeaways
✓Required ticket attributes (ID, plate, time) go in builder constructor
✓Optional attributes (spot, attendant, permit) set via fluent methods
✓Validation happens in build() checking cross-field business rules
✓Immutable ticket prevents modification after issuance
✓Nested builder accesses private ticket constructor for encapsulation
📌 Examples
1Valet parking assigns spot after ticket creation
2Automated entry creates tickets without attendant ID
3Disabled permit requires validation of spot accessibility