Skip to main content

Restaurant Management

Design the system behind a dine-in restaurant: seat a party at a table, take an order off the menu, fire it to the kitchen, and serve it. The prompt has three actors - host, server, kitchen - who each touch the same order at a different point in its life, which is exactly where a design either stays clean or turns into one class with everyone's logic crammed inside it.

Requirements

Functional

  • A Table is FREE, RESERVED, or OCCUPIED; seating a party changes it to OCCUPIED and clearing it changes it back to FREE.
  • An Order is opened against an occupied table and holds a list of OrderItems, each referencing a MenuItem and a quantity.
  • Once submitted, an order moves PLACED -> IN_KITCHEN -> READY -> SERVED; items cannot be added after it leaves PLACED.
  • The kitchen sees only items it needs to prepare, grouped by station (grill, cold, dessert), not the whole order as a flat undifferentiated list.

Non-functional

  • A MenuItem going 86'd (out of stock) mid-service must block new orders for it without the table or order code needing to know inventory exists.
  • Kitchen station routing (today: 3 fixed stations) must extend to new stations without editing Order or OrderItem.

Design

Order is the object that outlives a single interaction between any two actors: the host opens the table, the server builds and submits the order, the kitchen advances its status, and the server serves it - Order is the one thing all four steps read and mutate, exactly the role Ticket plays in Parking Lot as the object that carries state across a gap in time. Each MenuItem carries its own station, so grouping for the kitchen view is a partition, not a lookup table maintained somewhere else.

HostServerTableOrderKitchenseatParty()1new Order(table)2addItem(menuItem, qty)3submit()4groupByStation()5markReady()6
  1. 1Seating only ever touches the table - it has no idea an order will follow.
  2. 2The server opens an order against the now-occupied table; this is the object every later actor shares.
  3. 3Each add checks the order is still PLACED and the item is still available, both inside this one call.
  4. 4Submitting flips the order to IN_KITCHEN; addItem() is no longer legal from this point on.
  5. 5The kitchen asks for its own view of the same order - grouped by station, never the whole flat list.
  6. 6The kitchen advances status when done; it never edits the item list, only the status it owns the transition into.

Availability is a property of MenuItem, checked once at the moment an item is added to an order - the table and the order status machinery never reference stock at all.

Class diagram

Table- id: string- status: TableStatus+ seatParty()+ clear()Order- table: Table- items: List<OrderItem>- status: OrderStatus+ addItem(item, qty)+ submit()+ markReady()+ groupByStation(): Map<Station, List<OrderItem>>OrderItem- menuItem: MenuItem- quantity: intMenuItem- name: string- price: double- station: Station- available: bool
uses
Order carries OrderItems from PLACED to SERVED; each MenuItem's station groups the kitchen's view without Order knowing stations exist.

Code

import java.util.*;
import java.util.stream.Collectors;
 
enum TableStatus { FREE, RESERVED, OCCUPIED }
enum OrderStatus { PLACED, IN_KITCHEN, READY, SERVED }
enum Station { GRILL, COLD, DESSERT }
 
class Table {
final String id;
TableStatus status = TableStatus.FREE;
 
Table(String id) {
this.id = id;
}
 
void seatParty() {
status = TableStatus.OCCUPIED;
}
 
void clear() {
status = TableStatus.FREE;
}
}
 
class MenuItem {
final String name;
final double price;
final Station station;
boolean available = true;
 
MenuItem(String name, double price, Station station) {
this.name = name;
this.price = price;
this.station = station;
}
}
 
class OrderItem {
final MenuItem menuItem;
final int quantity;
 
OrderItem(MenuItem menuItem, int quantity) {
this.menuItem = menuItem;
this.quantity = quantity;
}
}
 
class Order {
final Table table;
private final List<OrderItem> items = new ArrayList<>();
private OrderStatus status = OrderStatus.PLACED;
 
Order(Table table) {
this.table = table;
}
 
void addItem(MenuItem menuItem, int quantity) {
if (status != OrderStatus.PLACED) {
throw new IllegalStateException("Cannot add items once order has left PLACED");
}
if (!menuItem.available) {
throw new IllegalStateException(menuItem.name + " is unavailable");
}
items.add(new OrderItem(menuItem, quantity));
}
 
void submit() {
status = OrderStatus.IN_KITCHEN;
}
 
void markReady() {
status = OrderStatus.READY;
}
 
void markServed() {
status = OrderStatus.SERVED;
table.clear();
}
 
Map<Station, List<OrderItem>> groupByStation() {
return items.stream().collect(Collectors.groupingBy(i -> i.menuItem.station));
}
 
OrderStatus getStatus() { return status; }
}

Design decisions

  • OrderStatus transitions are enforced on Order, and adding an item is only legal in PLACED. Once an order reaches the kitchen, the printed ticket is the source of truth for what's cooking; letting addItem() succeed after IN_KITCHEN would silently desync the two. Checking status inside addItem() itself means there's no code path that can add an item to a submitted order, not even a mistake in a future caller.
  • MenuItem.available is a flag the item owns, checked at addItem() time - not a separate inventory service Order calls into. For this scope, availability is binary and item-local, so putting it on the item keeps the dependency one-directional: Order reads a field on MenuItem it already holds a reference to, rather than reaching out to a new subsystem.
  • Kitchen station grouping is a groupByStation() query over OrderItem.menuItem.station, not a field that lives on Order. The order itself has no concept of stations; the grouping is a view the kitchen asks for, computed from data that's already there. Adding a fourth station is a new enum value, not a new field anywhere.
  • What's missing for a real system: splitting a check across multiple diners at one table needs Order to support partial-item ownership this design doesn't model (every item here belongs to the table's one order), and course timing (appetizers fire to the kitchen before entrees) needs per-item submission rather than the whole order moving to IN_KITCHEN at once - both left out to keep the four-status lifecycle the whole focus.
0%0 of 122 pages studied