In this chapter, we will explore the design of a Restaurant Management System. The goal is to create classes that represent the system’s essential components, such as menus, reservations, and tables. We will develop a system that supports critical functions like booking reservations, managing orders, and assigning tables, ensuring the design is both straightforward and flexible for future enhancements.
New here? In plain terms, this is what we’re building and why — with an everyday analogy to anchor your intuition before the deep dive below.
A restaurant system models the whole floor: tables, a menu, orders travelling to the kitchen, and a bill at the end. Giving each role — table, order, menu item, payment — its own object keeps the waitstaff, kitchen, and cashier in sync as diners are seated, served, and checked out.
The first step in designing a Restaurant Management System is to clarify the requirements and define the scope. Here’s an example of a typical prompt an interviewer might present:
“Picture yourself planning a dinner outing on a Friday night. You call the restaurant to reserve a table for your group, check available times, and secure a spot. When you arrive, the staff assigns your party to the reserved table, takes your order, and later presents the bill. Behind the scenes, the system is smoothly handling table reservations, tracking orders, and calculating costs. Now, let’s design a Restaurant Management System that manages all of this.”
Here is an example of how a conversation between a candidate and an interviewer might unfold:
Candidate: Let’s start by setting the scope. I assume the system manages reservations, menu, order tracking, and payments. For now, I focus on reservations and order management. Does that work?
Interviewer: That’s a reasonable starting point
Candidate: Does the system allow customers to make and manage their reservations?
Interviewer: Yes, customers can book tables for a future date and time based on availability.
Candidate: How does the system determine if a table is available for a reservation?
Interviewer: It checks for a table that fits the party size and is free at the requested time. Each reservation reserves a table for exactly one hour, so it’s available if no other booking overlaps with that hour.
Candidate: Does the system let customers cancel a reservation after making it?
Interviewer: Yes, customers can cancel reservations.
Candidate: When a party with a reservation arrives, do they automatically get their reserved table?
Interviewer: Yes, they do. They arrive with their name, and the system uses it to find their reservation, which already has a table assigned to it.
Candidate: What happens when a walk-in party arrives for dine-in without prior reservation?
Interviewer: The system should assign walk-in parties to tables based on current availability and their party size.
Candidate: Does the system allow orders to be altered or removed after they’re placed?
Interviewer: Yes, you can remove items or adjust their quantities.
Candidate: Does the system track the status of orders?
Interviewer: Yes, it keeps track of their progress.
Candidate: Are there rules for splitting the bill at checkout?
Interviewer: For now, just present a single total bill amount.
Based on the questions and answers, we can now list the functional requirements for our restaurant management system.
Reservations
Walk-in seating
Order management
Billing
Below are the non-functional requirements:
With these requirements, we are ready to model the objects for the core system.
Let’s identify the core objects of the restaurant management system.
You’re set to dive into the heart of the object-oriented design interview: crafting classes and interfaces, shaping data and state through attributes, wrapping logic in methods, and linking your classes with clear relationships.
Below, we detail each class, its purpose, and its responsibilities, ensuring a clear separation of concerns.
The Menu class represents the restaurant’s menu, storing menu items in a map with names as keys to quickly retrieve them for ordering. It separates menu data from the Restaurant and Table classes, enabling Table to order items by holding a collection of MenuItem objects that list all available choices.
Below is the representation of this class.
The MenuItem defines each item on the menu, holding its name, description, price, and category for use in orders. The Menu class uses these items to provide the list of choices, and the Table class records them as ordered items for order management and updates. The Category enum assigns each item a type, such as main course, appetizer, or dessert, to group them on the menu.
The UML diagram below illustrates this structure.
The Table class models restaurant tables. Some of the attributes, like capacity and tableId, rarely change. But other attributes, like reservations and orderedItems, associated with the table represent current-state data that changes over time.
Its purpose is to oversee a table’s current use, tying into the Layout class for availability checks and the OrderItem class to handle what’s being served. It includes methods to add or remove orders, tally up bills, and check availability at specific times.
Here is the representation of this class.
The Layout class oversees all restaurant tables, organizing them by ID and capacity to pinpoint the right one for each booking. Its purpose is to streamline table assignments, working with ReservationManager to match parties to available tables and relying on Table to confirm free slots. It handles this by finding a table that fits the party size and is available at the requested time, keeping the process efficient.
Design choice: We isolate table organization in the Layout class to optimize assignment efficiency and separate it from menu and order logic managed by the Menu and Table classes. Alternatively, integrating table assignment into the Restaurant class could simplify the design but would overburden its facade role, mixing high-level coordination with low-level table management.
Below is the representation of the Layout class.
The OrderItem class represents each item a customer orders, linking it to a specific MenuItem to provide details like price for the Table class. Its purpose is to track the status of ordered items, allowing the Table class to calculate costs accurately and add or remove items as requested. The class is created when customers place orders, using the Status enum, set to values like pending or delivered, to indicate the item’s current state.
The UML diagram below illustrates this structure.
The ReservationManager class handles reservation scheduling by finding available times, creating reservations, and processing cancellations. It stores all Reservation objects and uses a reference to the Layout class to manage table assignments. This reference enables the class to verify table availability and assign suitable tables for each reservation based on party size and time.
Below is the representation of this class.
The Reservation class represents a single booking managed by the ReservationManager class, storing the party name, number of people, reservation time, and assigned table. It serves to hold all details of a reservation, enabling the ReservationManager class to schedule and cancel bookings effectively.
The class is contained within ReservationManager as one of its entries, maintaining a structured and accessible set of reservation data.
The Restaurant class serves as the primary interface and facade for the restaurant management system. It coordinates core user-facing operations, such as managing reservations, assigning tables, processing orders, and handling checkout.
It simplifies access to these features by delegating tasks to other classes:
This delegation keeps the Restaurant class focused and manageable, organizing the system into distinct components that ensure clarity and ease of maintenance through a structured use of composition.
Design choice: We structure the Restaurant as a facade to unify system operations, delegating tasks to maintain a clean interface and modularity. We can design the Restaurant class as a central controller managing all logic internally, but that would increase its complexity and reduce scalability by centralizing responsibilities.
Below is the representation of this class.
Next, we’ll connect these objects in a class diagram to visualize their relationships.
Below is the complete class diagram of our restaurant management system.
With this structure in place, let’s move on to implement the code that brings this design to life.
In this section, we’ll implement the core functionalities of the Restaurant Management System, focusing on key areas such as managing menu items, scheduling and canceling reservations, assigning tables for bookings and walk-ins, and processing orders with billing through a table-based system.
The Menu class manages the restaurant’s menu by storing MenuItem objects in a HashMap, using each item’s name as the key to enable fast retrieval for order processing. It provides methods to add an item with addItem, retrieve a specific item using getItem, and access the full menu through getMenuItems as a read-only view.
Implementation Choice: We selected HashMap to store MenuItem objects for its quick lookup performance through key-based access, ensuring efficient retrieval for the Table class during order processing, whereas a List alternative, while simpler for storage, requires a linear search that slows access time.
The definition of the Menu class is given below.
public class Menu {
private final Map<String, MenuItem> menuItems = new HashMap<>();
// Adds a new item to the menu
public void addItem(MenuItem item) {
menuItems.put(item.getName(), item);
}
public MenuItem getItem(String name) {
return menuItems.get(name);
}
public Map<String, MenuItem> getMenuItems() {
return Collections.unmodifiableMap(menuItems);
}
}
With the Menu defined, let’s detail its individual items in MenuItem.
The MenuItem class represents an individual item on the menu, storing its name, description, price, and category as private final fields to ensure immutability. This class uses BigDecimal for the price field instead of a floating-point type like float or double. BigDecimal is better suited for financial data, as it provides precise control over decimal values and helps avoid rounding errors or precision issues that can arise with floating-point calculations.
// Represents a single item available on the restaurant menu
public class MenuItem {
private final String name;
private final String description;
private final BigDecimal price;
private final Category category;
public MenuItem(String name, String description, BigDecimal price, Category category) {
this.name = name;
this.description = description;
this.price = price;
this.category = category;
}
// Enumeration of possible menu item categories
public enum Category {
MAIN,
APPETIZER,
DESSERT
} // getter methods are omitted for brevity
}
Having established the menu’s contents, let’s model the restaurant’s tables with Table.
The Table class represents a restaurant table, storing its fixed properties, such as tableId and capacity, while maintaining its bookings and orders by updating reservations and orderedItems. Its purpose is to manage a table’s reservations and orders, using Layout to confirm available time slots and OrderItem to provide price and status details.
Below is the implementation of this class.
// Represents a table in the restaurant with its properties and current state
public class Table {
// immutable properties
private final int tableId;
private final int capacity;
// current state
private final Map<LocalDateTime, Reservation> reservations = new HashMap<>();
private final Map<MenuItem, List<OrderItem>> orderedItems = new HashMap<>();
public Table(int tableId, int capacity) {
this.tableId = tableId;
this.capacity = capacity;
}
// Calculates the total bill amount for all ordered items at this table
public BigDecimal calculateBillAmount() {
return orderedItems.values().stream()
.flatMap(List::stream)
.map(OrderItem::getItem)
.map(MenuItem::getPrice)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
// Adds multiple orders of the same menu item to the table
public void addOrder(MenuItem item, int quantity) {
for (int i = 0; i < quantity; i++) {
addOrder(item);
}
}
// Adds a single menu item to the table's order
public void addOrder(MenuItem item) {
List<OrderItem> orderItems = orderedItems.get(item);
if (orderItems == null) {
orderItems = new ArrayList<>();
orderedItems.put(item, orderItems);
orderItems.add(new OrderItem(item));
} else {
orderItems.add(new OrderItem(item));
}
}
// Removes a menu item from the table's order
public void removeOrder(MenuItem item) {
List<OrderItem> orderItems = orderedItems.get(item);
if (orderItems != null) {
orderItems.remove(0);
if (orderItems.isEmpty()) {
orderedItems.remove(item);
}
}
}
// Checks if the table is available at a specific time
public boolean isAvailableAt(LocalDateTime reservationTime) {
return !reservations.containsKey(reservationTime);
}
// Adds a reservation to this table
public void addReservation(Reservation reservation) {
reservations.put(reservation.getTime(), reservation);
}
// Removes a reservation from this table for a specific time
public void removeReservation(LocalDateTime reservationTime) {
reservations.remove(reservationTime);
}
// getter methods are omitted for brevity
}
The class uses a Map with reservation times as keys to store reservations. This setup enables quick availability checks through the isAvailableAt method. It also uses a separate Map with MenuItem keys to manage orderedItems. This structure supports the addOrder method to include items, the removeOrder method to remove them, and the calculateBillAmount method for billing.
With tables in place, let’s organize them efficiently using Layout.
The Layout class represents the seating arrangement of the entire restaurant, managing all tables to support efficient assignments for reservations and walk-ins. It organizes tables using two indexing methods:
Below is the implementation of this class.
// Manages the collection of tables in the restaurant and their arrangement
public class Layout {
private final Map<Integer, Table> tablesById = new HashMap<>();
// Groups tables by their capacity for efficient table assignment, sorted from smallest to
// largest capacity
private final SortedMap<Integer, Set<Table>> tablesByCapacity = new TreeMap<>();
public Layout(List<Integer> tableCapacities) {
for (int i = 0; i < tableCapacities.size(); i++) {
int capacity = tableCapacities.get(i);
Table table = new Table(i, capacity);
tablesById.put(i, table);
tablesByCapacity.computeIfAbsent(capacity, k -> new HashSet<>()).add(table);
}
}
// Finds the smallest available table that can accommodate a party of the given size at the
// given time
public Table findAvailableTable(int partySize, LocalDateTime reservationTime) {
for (Set<Table> tables : tablesByCapacity.tailMap(partySize).values()) {
for (Table table : tables) {
if (table.isAvailableAt(reservationTime)) {
return table;
}
}
}
return null;
}
}
Implementation Choice: We chose SortedMap structures for tablesByCapacity. This structure provides sorted key access that supports efficient range searches by capacity. This efficiency is crucial for the findAvailableTable method to match tables to party sizes. A basic Map lacks sorting capability. It would require additional logic to identify the smallest suitable table, making it less efficient.
Now that tables are arranged, let’s manage individual order items with OrderItem.
The OrderItem class defines each item ordered by a customer, storing a reference to a specific MenuItem and maintaining its current status to track its state during order processing.
The class includes methods to update the order’s state:
// Represents a food item ordered by a customer with its current status in the order process
public class OrderItem {
private final MenuItem item;
private Status status = Status.PENDING;
public OrderItem(MenuItem item) {
this.item = item;
}
// Updates the status to indicate the item has been sent to the kitchen
public void sendToKitchen() {
if (status == Status.PENDING) status = Status.SENT_TO_KITCHEN;
}
// Updates the status to indicate the item has been delivered to the customer
public void deliverToCustomer() {
if (status == Status.SENT_TO_KITCHEN) status = Status.DELIVERED;
}
// Updates the status to indicate the item has been canceled
public void cancel() {
if (status == Status.PENDING || status == Status.SENT_TO_KITCHEN) {
status = Status.CANCELED;
}
}
// getter methods are omitted for brevity
}
With orders tracked, let’s oversee reservations through ReservationManager.
The ReservationManager class oversees all reservations in the restaurant, managing their scheduling, creation, and cancellation to ensure tables are assigned accurately. It serves as a central coordinator, connecting to the Layout class to locate available tables and storing Reservation objects to maintain booking details.
Here is the implementation of this class.
// Manages all reservations for the restaurant and handles table assignments
public class ReservationManager {
private final Layout layout;
private final Set<Reservation> reservations = new HashSet<>();
// Constructor that takes the restaurant's table layout
public ReservationManager(Layout layout) {
this.layout = layout;
}
// Finds potential time slots for a reservation within the given time range and party size
public LocalDateTime[] findAvailableTimeSlots(
LocalDateTime rangeStart, LocalDateTime rangeEnd, int partySize) {
// checking every hour in the time range
LocalDateTime current = rangeStart;
List<LocalDateTime> possibleReservations = new ArrayList<>();
while (!current.isAfter(rangeEnd)) {
Table availableTable = layout.findAvailableTable(partySize, current);
if (availableTable != null) {
possibleReservations.add(current);
}
current = current.plusHours(1);
}
return possibleReservations.toArray(new LocalDateTime[0]);
}
// Creates a reservation for a specific time, party size and name
public Reservation createReservation(
String partyName, int partySize, LocalDateTime desiredTime) {
desiredTime = desiredTime.truncatedTo(ChronoUnit.HOURS);
Table table = layout.findAvailableTable(partySize, desiredTime);
Reservation reservation = new Reservation(partyName, partySize, desiredTime, table);
table.addReservation(reservation);
reservations.add(reservation);
return reservation;
}
// Removes an existing reservation
public void removeReservation(
String partyName, int partySize, LocalDateTime reservationTime) {
// Find matching reservation before removing it
for (Reservation reservation : new HashSet<>(reservations)) {
if (reservation.getTime().equals(reservationTime)
&& reservation.getPartySize() == partySize
&& reservation.getPartyName().equals(partyName)) {
// Clear the reservation from the table first
Table table = reservation.getAssignedTable();
table.removeReservation(reservationTime);
// Then remove from the reservation collection
reservations.remove(reservation);
return;
}
}
}
// getter methods are omitted for brevity
}
Implementation Choice: We chose a Set to store Reservation objects because its unique entry enforcement prevents duplicate bookings, aligning with the need to manage reservations accurately. An alternative could use a List, which allows simpler iteration but risks duplicates unless additional checks are added, or a Map with time-based keys, which could speed up lookups but complicate removal by requiring keys that combine multiple fields, like time, party size, and name, less suited for the system’s focus on reservation uniqueness.
Next, let’s define the reservation entries with Reservation.
The Reservation class is a simple, immutable entity class that stores essential reservation details, including the party name, number of people, reservation time, and assigned table. It serves as the foundational unit for the ReservationManager class, holding the data needed to manage bookings effectively.
// Represents a reservation made at the restaurant for a specific party, time and table
public class Reservation {
private final String partyName;
private final int partySize;
private final LocalDateTime time;
private final Table assignedTable;
public Reservation(
String partyName, int partySize, LocalDateTime time, Table assignedTable) {
this.partyName = partyName;
this.partySize = partySize;
this.time = time;
this.assignedTable = assignedTable;
}
// getter methods are omitted for brevity
}
Finally, let’s unify these components in the Restaurant.
The Restaurant class acts as the central interface for the restaurant management system, offering methods to book reservations, seat walk-in parties, place orders, and compute bills. It follows the facade design pattern to unify these features, delegating reservation and walk-in seating tasks to ReservationManager, which uses Layout to find available tables, order placement, and billing to Table, which uses Menu to access items.
Below is the implementation of this class.
// Main restaurant class that manages reservations, orders, and tables
public class Restaurant {
private final String name;
private final Menu menu;
private final Layout layout;
private final ReservationManager reservationManager;
public Restaurant(String name, Menu menu, Layout layout) {
this.name = name;
this.menu = menu;
this.layout = layout;
this.reservationManager = new ReservationManager(layout);
}
// Finds possible reservation times within a time range for a party of specified size
public LocalDateTime[] findAvailableTimeSlots(
LocalDateTime rangeStart, LocalDateTime rangeEnd, int partySize) {
return reservationManager.findAvailableTimeSlots(rangeStart, rangeEnd, partySize);
}
// Creates a reservation for a party at the specified time
public Reservation createScheduledReservation(
String partyName, int partySize, LocalDateTime time) {
return reservationManager.createReservation(partyName, partySize, time);
}
// Removes an existing reservation
public void removeReservation(
String partyName, int partySize, LocalDateTime reservationTime) {
reservationManager.removeReservation(partyName, partySize, reservationTime);
}
// Creates a reservation for a party without prior reservation
public Reservation createWalkInReservation(String partyName, int partySize) {
return reservationManager.createReservation(partyName, partySize, LocalDateTime.now());
}
// Adds an item to a table's order
public void orderItem(Table table, MenuItem item) {
table.addOrder(item);
}
// Removes an item from a table's order
public void cancelItem(Table table, MenuItem item) {
table.removeOrder(item);
}
// Calculates the bill amount for a table
public BigDecimal calculateTableBill(Table table) {
return table.calculateBillAmount();
}
// getter methods are omitted for brevity
}
Implementation Choice: We designed the Restaurant as a facade without its data structures. It delegates all operations to Menu, Layout, ReservationManager, and Table. This approach maintains a lightweight structure that simplifies system access. An alternative could have Restaurant store reservations or orders internally using a Map or List. That design would increase complexity and reduce modularity by centralizing responsibilities.
Having built the core system, let’s explore an enhancement in the deep dive to extend its capabilities.
A runnable front-of-house flow: seat a table, take orders from the menu, then print an itemised bill with tax — each role (table, menu item, order, bill) modelled as its own object.
def money(cents):
return "$%.2f" % (cents / 100.0)
class MenuItem:
def __init__(self, name, price):
self.name = name
self.price = price # price in cents
class Table:
def __init__(self, number):
self.number = number
self.occupied = False
self.order = []
class Restaurant:
def __init__(self, menu, tables):
self.menu = menu # name -> MenuItem
self.tables = tables # number -> Table
def seat(self, number):
self.tables[number].occupied = True
return "Table %d seated" % number
def order_item(self, number, item_name):
item = self.menu[item_name]
self.tables[number].order.append(item)
return "ordered %s (%s)" % (item.name, money(item.price))
def bill(self, number):
table = self.tables[number]
lines = ["Bill for table %d:" % number]
subtotal = 0
for item in table.order:
subtotal += item.price
lines.append(" %s = %s" % (item.name, money(item.price)))
tax = subtotal * 10 // 100 # 10% tax, in whole cents
total = subtotal + tax
lines.append("Subtotal: " + money(subtotal))
lines.append("Tax (10%): " + money(tax))
lines.append("Total: " + money(total))
return "\n".join(lines)
def free_table(self, number):
table = self.tables[number]
table.occupied = False
table.order = []
return "Table %d freed" % number
def main():
menu = {
"Burger": MenuItem("Burger", 800),
"Fries": MenuItem("Fries", 300),
"Soda": MenuItem("Soda", 200),
}
tables = {1: Table(1), 2: Table(2)}
restaurant = Restaurant(menu, tables)
print(restaurant.seat(1))
print(restaurant.order_item(1, "Burger"))
print(restaurant.order_item(1, "Fries"))
print(restaurant.order_item(1, "Soda"))
print(restaurant.bill(1))
print(restaurant.free_table(1))
if __name__ == "__main__":
main()#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <cstdio>
static std::string money(int cents) {
char buffer[16];
std::snprintf(buffer, sizeof(buffer), "$%.2f", cents / 100.0);
return std::string(buffer);
}
struct MenuItem {
std::string name;
int price; // price in cents
MenuItem() : name(""), price(0) {}
MenuItem(std::string name, int price) : name(name), price(price) {}
};
struct Table {
int number;
bool occupied;
std::vector<MenuItem> order;
Table() : number(0), occupied(false) {}
Table(int number) : number(number), occupied(false) {}
};
class Restaurant {
public:
std::map<std::string, MenuItem> menu;
std::map<int, Table> tables;
Restaurant(std::map<std::string, MenuItem> menu, std::map<int, Table> tables)
: menu(menu), tables(tables) {}
std::string seat(int number) {
tables[number].occupied = true;
return "Table " + std::to_string(number) + " seated";
}
std::string orderItem(int number, const std::string& itemName) {
MenuItem item = menu[itemName];
tables[number].order.push_back(item);
return "ordered " + item.name + " (" + money(item.price) + ")";
}
std::string bill(int number) {
Table& table = tables[number];
std::string out = "Bill for table " + std::to_string(number) + ":";
int subtotal = 0;
for (MenuItem& item : table.order) {
subtotal += item.price;
out += "\n " + item.name + " = " + money(item.price);
}
int tax = subtotal * 10 / 100; // 10% tax, in whole cents
int total = subtotal + tax;
out += "\nSubtotal: " + money(subtotal);
out += "\nTax (10%): " + money(tax);
out += "\nTotal: " + money(total);
return out;
}
std::string freeTable(int number) {
Table& table = tables[number];
table.occupied = false;
table.order.clear();
return "Table " + std::to_string(number) + " freed";
}
};
int main() {
std::map<std::string, MenuItem> menu;
menu["Burger"] = MenuItem("Burger", 800);
menu["Fries"] = MenuItem("Fries", 300);
menu["Soda"] = MenuItem("Soda", 200);
std::map<int, Table> tables;
tables[1] = Table(1);
tables[2] = Table(2);
Restaurant restaurant(menu, tables);
std::cout << restaurant.seat(1) << "\n";
std::cout << restaurant.orderItem(1, "Burger") << "\n";
std::cout << restaurant.orderItem(1, "Fries") << "\n";
std::cout << restaurant.orderItem(1, "Soda") << "\n";
std::cout << restaurant.bill(1) << "\n";
std::cout << restaurant.freeTable(1) << "\n";
return 0;
}import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
class MenuItem {
final String name;
final int price; // price in cents
MenuItem(String name, int price) { this.name = name; this.price = price; }
}
class Table {
final int number;
boolean occupied = false;
final List<MenuItem> order = new ArrayList<>();
Table(int number) { this.number = number; }
}
class Restaurant {
final Map<String, MenuItem> menu;
final Map<Integer, Table> tables;
Restaurant(Map<String, MenuItem> menu, Map<Integer, Table> tables) {
this.menu = menu; this.tables = tables;
}
static String money(int cents) { return String.format("$%.2f", cents / 100.0); }
String seat(int number) {
tables.get(number).occupied = true;
return "Table " + number + " seated";
}
String orderItem(int number, String itemName) {
MenuItem item = menu.get(itemName);
tables.get(number).order.add(item);
return "ordered " + item.name + " (" + money(item.price) + ")";
}
String bill(int number) {
Table table = tables.get(number);
StringBuilder sb = new StringBuilder("Bill for table " + number + ":");
int subtotal = 0;
for (MenuItem item : table.order) {
subtotal += item.price;
sb.append("\n ").append(item.name).append(" = ").append(money(item.price));
}
int tax = subtotal * 10 / 100; // 10% tax, in whole cents
int total = subtotal + tax;
sb.append("\nSubtotal: ").append(money(subtotal));
sb.append("\nTax (10%): ").append(money(tax));
sb.append("\nTotal: ").append(money(total));
return sb.toString();
}
String freeTable(int number) {
Table table = tables.get(number);
table.occupied = false;
table.order.clear();
return "Table " + number + " freed";
}
}
public class Main {
public static void main(String[] args) {
Map<String, MenuItem> menu = new LinkedHashMap<>();
menu.put("Burger", new MenuItem("Burger", 800));
menu.put("Fries", new MenuItem("Fries", 300));
menu.put("Soda", new MenuItem("Soda", 200));
Map<Integer, Table> tables = new LinkedHashMap<>();
tables.put(1, new Table(1));
tables.put(2, new Table(2));
Restaurant restaurant = new Restaurant(menu, tables);
System.out.println(restaurant.seat(1));
System.out.println(restaurant.orderItem(1, "Burger"));
System.out.println(restaurant.orderItem(1, "Fries"));
System.out.println(restaurant.orderItem(1, "Soda"));
System.out.println(restaurant.bill(1));
System.out.println(restaurant.freeTable(1));
}
}Sample run — identical across all three languages:
Table 1 seated
ordered Burger ($8.00)
ordered Fries ($3.00)
ordered Soda ($2.00)
Bill for table 1:
Burger = $8.00
Fries = $3.00
Soda = $2.00
Subtotal: $13.00
Tax (10%): $1.30
Total: $14.30
Table 1 freed
In this section, we’ll explore an enhancement to the Restaurant Management System by improving order handling during peak times. We’ll focus on adding a centralized order queue tracking mechanism to streamline kitchen coordination, ensure scalability, and maintain consistency with the system’s modular design.
Consider a high-traffic scenario, such as a busy Friday evening at the restaurant, where a significant volume of orders is received, staff work diligently to communicate these to the kitchen, and cancellations accumulate. In the existing system, the Table class directly governs the status of OrderItem instances (e.g., through methods like sendToKitchen() and deliverToCustomer()). However, this decentralized structure lacks a cohesive overview of order progression across all tables. Consequently, staff face challenges in prioritizing time-sensitive orders, monitoring kitchen delays, or verifying cancellations without individually inspecting each table’s state. This approach introduces risks of inconsistency and undermines effective coordination during periods of elevated demand.
To address these limitations, we propose an enhancement by introducing a centralized OrderManager class responsible for queuing and processing order-related actions. Let’s take a closer look.
Implementation steps:
To implement this enhancement effectively, we’ll follow these steps:
Step 1: Define a Command Interface for Order Actions: The staff needs a consistent way to issue actions like sending an order to the kitchen or canceling it, improving reliability over direct calls. We define an interface called OrderCommand with a single method, execute(), which concrete classes will implement to perform their tasks.
Here’s the code for the OrderCommand interface:
public interface OrderCommand {
void execute();
}
Step 2: Implement Concrete Command Classes: To give staff a flexible way to manage distinct order actions and prepare them for centralized queuing, we build specific classes for each task, unlike the original system, where Table directly updated OrderItem statuses.
Here’s the code for the action classes:
// Command that handles sending order items to the Kitchen
public class SendToKitchenCommand implements OrderCommand {
private final OrderItem orderItem;
public SendToKitchenCommand(OrderItem orderItem) {
this.orderItem = orderItem;
}
@Override
public void execute() {
orderItem.sendToKitchen();
}
} // Command that handles delivery of order items
public class DeliverCommand implements OrderCommand {
private final OrderItem orderItem;
public DeliverCommand(OrderItem orderItem) {
this.orderItem = orderItem;
}
@Override
public void execute() {
orderItem.deliverToCustomer();
}
} // Command that handles cancellations of order items
public class CancelCommand implements OrderCommand {
private final OrderItem orderItem;
public CancelCommand(OrderItem orderItem) {
this.orderItem = orderItem;
}
@Override
public void execute() {
orderItem.cancel();
}
}
Step 3: Introduce the OrderManager Class: To handle these actions efficiently, we introduce an OrderManager class. This class maintains a list of OrderCommand objects, allowing us to add commands with addCommand() as orders are placed. The executeCommands() method processes all queued commands in sequence and clears the list afterward, ensuring orders are managed in an organized way without directly altering the Table’s OrderItems.
Here’s the code for the OrderManager class:
public class OrderManager {
private final List<OrderCommand> commandQueue = new ArrayList<>();
// Adds a command to the queue for later execution
public void addCommand(OrderCommand command) {
commandQueue.add(command);
}
// Executes all commands in the queue and clears it
public void executeCommands() {
for (OrderCommand command : commandQueue) {
command.execute();
}
commandQueue.clear();
}
}
Step 4: Integrate with the Restaurant Class: To connect this system to staff actions, we update the Restaurant class to use OrderManager for order handling. When staff place an order, orderItem adds it to the table and queues a SendToKitchenCommand to send it to the kitchen. Similarly, cancelItem and deliverItem queue their respective commands, ensuring all actions flow through the centralized system for consistent tracking and execution.
Here’s the essential code for integrating OrderManager into the Restaurant:
public class Restaurant {
// ... fields unchanged ...
private final OrderManager orderManager;
public Restaurant(String name, Menu menu, Layout layout) {
// ... fields unchanged ...
this.orderManager = new OrderManager();
}
// Adds an item to a table's order and sends it to the kitchen
public void orderItem(Table table, MenuItem item) {
table.addOrder(item);
// Get the last added order item
List<OrderItem> orderItems = table.getOrderedItems().get(item);
if (orderItems != null && !orderItems.isEmpty()) {
OrderItem lastOrder = orderItems.get(orderItems.size() - 1);
OrderCommand sendToKitchen = new SendToKitchenCommand(lastOrder);
orderManager.addCommand(sendToKitchen);
orderManager.executeCommands();
}
}
// Removes an item from a table's order and cancels it
public void cancelItem(Table table, MenuItem item) {
List<OrderItem> orderItems = table.getOrderedItems().get(item);
if (orderItems != null && !orderItems.isEmpty()) {
OrderItem lastOrder = orderItems.get(orderItems.size() - 1);
OrderCommand cancelOrder = new CancelCommand(lastOrder);
orderManager.addCommand(cancelOrder);
orderManager.executeCommands();
table.removeOrder(item);
}
}
// Delivers an item to the customer
public void deliverItem(Table table, MenuItem item) {
List<OrderItem> orderItems = table.getOrderedItems().get(item);
if (orderItems != null && !orderItems.isEmpty()) {
OrderItem lastOrder = orderItems.get(orderItems.size() - 1);
OrderCommand deliverOrder = new DeliverCommand(lastOrder);
orderManager.addCommand(deliverOrder);
orderManager.executeCommands();
}
}
// ... other methods unchanged ...
}
What we’ve just implemented follows a well-known software design pattern called the Command Pattern.
Definition: The Command is a behavioral design pattern that encapsulates a request as an independent object, containing all the details needed to carry it out. This encapsulation allows you to treat requests as parameters for methods, delay or schedule their execution.
In this pattern:
The UML diagram below illustrates this structure.
With our restaurant management system designed and implemented, let’s wrap up with key takeaways.
In this chapter, we gathered requirements for the Restaurant Management System through a series of detailed questions and answers. We then identified the core objects involved, designed the class structure, and implemented the key components of the system.
A key takeaway from this design is the importance of modularity and adherence to the single responsibility principle. Each component, such as the Menu, ReservationManager, Layout, and Table classes, manages a distinct responsibility, ensuring the system remains maintainable and adaptable for future enhancements.
Our design choices, such as delegating operations in the Restaurant to act as a facade or using immutable MenuItem objects, prioritize flexibility and consistency. An alternative, like implementing reservation and order logic directly in the Restaurant class, may simplify the design but could increase complexity and reduce scalability by centralizing responsibilities. In an interview, revisiting these decisions and explaining their rationale demonstrates your ability to think critically about system design.
Congratulations on getting this far! Now give yourself a pat on the back. Good job!