A box office written with loose dictionaries and a chain of string checks works but has no structure. Refactor it into the classes of the target diagram, with the same prices and the same refusals.
A small box office sells event tickets. The code in the editor already works, but it keeps everything in dictionaries, decides prices with a chain of if checks on strings, and signals errors with one generic exception. Rewrite it as the classes of this target diagram, so the structure the diagram promises is the structure the code has. The behaviour (prices, refusals, refunds) stays the same.
%% title: The box office, target design
classDiagram
class BoxOffice {
+events list
+attendees list
+tickets list
+add_event(name, seat_count, base_price) Event
+register(attendee)
+sell(event_name, seat_number, attendee, kind) Ticket
+refund(ticket) int
+revenue() int
}
class Event {
+name str
+base_price int
+seats list
+free_seat() Seat
}
class Seat {
+number int
+ticket Ticket
+taken() bool
}
class Ticket {
+price int
}
class Attendee {
+name str
}
class TicketKind {
<<interface>>
+price(base_price) int
}
class StandardKind
class VipKind
class StudentKind {
+percent int
}
class BoxOfficeError
class SeatTaken
class SoldOut
BoxOffice "1" *-- "*" Event : events
BoxOffice "1" *-- "*" Ticket : tickets
BoxOffice "*" o-- "*" Attendee : attendees
Event "1" *-- "1..*" Seat : seats
Seat "1" --> "0..1" Ticket : sold as
Ticket "*" --> "1" Event : for
Ticket "*" --> "1" Seat : in
Ticket "*" --> "1" Attendee : to
BoxOffice ..> TicketKind : prices with
TicketKind <|.. StandardKind
TicketKind <|.. VipKind
TicketKind <|.. StudentKind
BoxOfficeError <|-- SeatTaken
BoxOfficeError <|-- SoldOutThe three biggest changes from the messy code: the if kind == "vip" chain becomes an interface with three implementations (BoxOffice only depends on it), the dictionaries become classes that create and hold each other as the diamonds say, and the one Exception becomes a small hierarchy.
Build this API (names are Python's; JavaScript and TypeScript use camelCase where they differ: seatCount, basePrice, addEvent, eventName, seatNumber, freeSeat):
BoxOfficeError is an exception (JavaScript: extends Error); SeatTaken and SoldOut are BoxOfficeErrors.TicketKind is the interface: price(base_price) is abstract (building it raises TypeError; JavaScript: Error containing interface). StandardKind: the base price. VipKind: double the base price. StudentKind(percent): base_price * (100 - percent) // 100.Attendee(name).Ticket(event, seat, attendee, price) holds those very objects and the price paid.Seat(number) with .number, .ticket (starts None / null) and taken() (true when it has a ticket).Event(name, seat_count, base_price) with .name, .base_price and .seats: it creates seat_count seats numbered from 1, and needs at least one (ValueError; JavaScript: Error containing at least one seat). free_seat() is the first untaken seat or None / null.BoxOffice() with .events, .attendees and .tickets, all empty. add_event(...) creates the event, keeps and returns it. register(attendee) keeps the very attendee.sell(event_name, seat_number, attendee, kind): the attendee must be registered, the event must exist and a given seat number must exist: each otherwise raises BoxOfficeError (message contains not registered, no event, no seat). A taken seat raises SeatTaken. With seat_number of None / null it takes the first free seat, and raises SoldOut when there is none. Otherwise it creates the Ticket priced by kind.price(event.base_price), puts it on the seat, keeps it in tickets and returns it. The kind is only a parameter: the box office never stores it. A refused call changes nothing.refund(ticket) frees the seat, removes the ticket and returns its price. A ticket that is not in tickets raises BoxOfficeError (message contains not an active ticket).revenue() is the sum of the prices of the tickets in tickets.The tests also check that the new code has no chain of string checks on the kind (no elif in Python, no else if in JavaScript or TypeScript): the interface is there so that the chain is not.
Write your code, then run 10 example tests and 3 hidden tests. Each run is limited to 8 seconds: an infinite loop gets stopped and the runtime restarts, your code stays.