Low-Level Design (LLD): Food Delivery System (Zomato / Swiggy Clone)
An end-to-end pedagogical breakdown of designing an enterprise food delivery application (named Tomato). This chapter covers requirement scoping, Object-Oriented Analysis and Design (OOAD), Unified Modeling Language (UML) structural decisions, design pattern integration, and modular C++ implementation.
1. Problem Statement and Interview Requirements Scoping
In a standard Low-Level Design (LLD) interview (typically 45–60 minutes), the objective is not to build an entire production-grade enterprise system with every micro-edge case. Rather, the interviewer assesses:
- How effectively you scope ambiguous requirements via proactive questions.
- How you model real-world business entities and map domain relationships (associations, aggregations, compositions, inheritance).
- How cleanly you apply Object-Oriented Design (OOD) principles (SOLID, DRY, Law of Demeter) and GoF design patterns.
- Your ability to produce clean, modular, and compilable code.
Problem Prompt: "Design an online food delivery platform like Zomato/Swiggy."
Requirement Breakdown
Note: Clarifying boundaries early prevents unbounded feature creep. For instance, determining whether the interview scope is User-Centric (browse, cart, checkout) or Delivery-Agent-Centric (driver assignment, routing) dictates whether driver entities and routing graphs need to be modeled. Here, the scope is firmly User-Centric.
2. End-to-End User Flow
Before modeling classes, the user journey must be aligned with the interviewer:
Critical Scoping Decisions Clarified with the Interviewer
- Third-Party Integrations: Payment execution and notification gateways are assumed to be external integrations. Our responsibility is to provide clean interfaces and strategies, not build banking settlement engines.
- Order Fulfillment Types:
- Delivery Order: Requires delivery address, delivery fee handling, and recipient details.
- Pickup Order: Requires restaurant address for customer self-collection.
3. Modeling Methodology: Bottom-Up vs. Top-Down
When transitioning from requirements to class structures, engineers generally choose between two modeling paradigms:
4. Core Domain Models & Entity Relationships
Understanding entity lifecycles dictates whether an association is Composition (strong ownership; child cannot exist without parent) or Aggregation (weak ownership; child has an independent lifecycle).
1. MenuItem (Model)
An immutable representation of an individual food dish offered by a restaurant.
- Attributes:
code(string/ID),name(string),price(double).
2. Restaurant (Model)
Represents the merchant partner offering food.
- Attributes:
id(auto-incrementing int),name(string),location(string),menu(vector<MenuItem>). - Relationship to
MenuItem: Modeled as Composition. A specific menu line-item tied to a restaurant's catalog does not exist independently within the system if the restaurant is deleted.
3. User & Cart (Models)
User: Holds identity attributes (userId,name,address) and an ownedCart.Cart: Holds a reference to the targetRestaurant, a list of selectedMenuItemobjects, and a computedtotalCost.- Relationship between
UserandCart: Composition. A cart belongs uniquely to a single user session; it does not float independently without an owner. - Relationship between
Cart,Restaurant, andMenuItem: Aggregation. The cart holds references to items and restaurants, but clearing or deleting a cart does not delete the merchant or their catalog items.
// Core models definition sample
class MenuItem {
private:
std::string code;
std::string name;
double price;
public:
MenuItem(std::string c, std::string n, double p) : code(c), name(n), price(p) {}
std::string getCode() const { return code; }
std::string getName() const { return name; }
double getPrice() const { return price; }
};
class Cart {
private:
Restaurant* restaurant;
std::vector<MenuItem> items;
public:
Cart() : restaurant(nullptr) {}
void setRestaurant(Restaurant* r) { restaurant = r; }
Restaurant* getRestaurant() const { return restaurant; }
void addItem(const MenuItem& item) { items.push_back(item); }
void clear() { items.clear(); restaurant = nullptr; }
bool isEmpty() const { return items.empty(); }
double getTotalCost() const {
double total = 0.0;
for (const auto& item : items) total += item.getPrice();
return total;
}
const std::vector<MenuItem>& getItems() const { return items; }
};
5. The Manager Pattern & Singleton Scope
To achieve loose coupling, operational components must not reach directly into unstructured lists of entities. Domain objects are mediated through dedicated managers.
Why Singleton for Managers?
Managers maintain the authoritative, centralized in-memory state (catalogs, registries, order histories). Instantiating multiple instances of RestaurantManager would cause split-brain state inconsistencies where Restaurant A is registered in Instance 1, but queried against Instance 2.
class RestaurantManager {
private:
std::vector<Restaurant*> restaurants;
static RestaurantManager* instance;
RestaurantManager() {} // Private Constructor
public:
static RestaurantManager* getInstance() {
if (!instance) {
instance = new RestaurantManager();
}
return instance;
}
void addRestaurant(Restaurant* r) {
restaurants.push_back(r);
}
std::vector<Restaurant*> searchByLocation(const std::string& loc) {
std::vector<Restaurant*> result;
for (auto* r : restaurants) {
if (r->getLocation() == loc) {
result.push_back(r);
}
}
return result;
}
};
RestaurantManager* RestaurantManager::instance = nullptr;
Similarly, OrderManager is a Singleton that acts as the single point of contact for storing, persisting, and listing placed orders.
6. Order Domain & The Factory Method Pattern
Fulfillment workflows differ widely between physical pickup and doorstep delivery. Modeling orders requires polymorphic inheritance combined with a Factory Pattern to decouple creation logic.
class Order {
protected:
static int nextOrderId;
int orderId;
User* user;
Restaurant* restaurant;
std::vector<MenuItem> items;
PaymentStrategy* paymentStrategy;
double totalCost;
std::string scheduledTime;
public:
Order() : orderId(++nextOrderId), user(nullptr), restaurant(nullptr),
paymentStrategy(nullptr), totalCost(0.0) {}
virtual ~Order() {}
virtual std::string getType() const = 0; // Pure virtual
void setOrderDetails(User* u, Restaurant* r, const std::vector<MenuItem>& itms,
PaymentStrategy* ps, double cost, const std::string& time) {
user = u;
restaurant = r;
items = itms;
paymentStrategy = ps;
totalCost = cost;
scheduledTime = time;
}
bool processPayment() {
if (!paymentStrategy) {
std::cout << "Error: No payment strategy assigned!\n";
return false;
}
return paymentStrategy->pay(totalCost);
}
int getOrderId() const { return orderId; }
User* getUser() const { return user; }
Restaurant* getRestaurant() const { return restaurant; }
const std::vector<MenuItem>& getItems() const { return items; }
double getTotalCost() const { return totalCost; }
std::string getScheduledTime() const { return scheduledTime; }
};
int Order::nextOrderId = 0;
class DeliveryOrder : public Order {
private:
std::string userAddress;
public:
std::string getType() const override { return "Delivery"; }
void setUserAddress(const std::string& addr) { userAddress = addr; }
std::string getUserAddress() const { return userAddress; }
};
class PickUpOrder : public Order {
private:
std::string restaurantAddress;
public:
std::string getType() const override { return "PickUp"; }
void setRestaurantAddress(const std::string& addr) { restaurantAddress = addr; }
std::string getRestaurantAddress() const { return restaurantAddress; }
};
Decoupling Creation with Factory Method
Orders can be instantiated immediately (NowOrderFactory) or reserved for future fulfillment (ScheduledOrderFactory).
class OrderFactory {
public:
virtual ~OrderFactory() {}
virtual Order* createOrder(const std::string& orderType, User* user, Restaurant* restaurant,
const std::vector<MenuItem>& items, PaymentStrategy* ps,
double totalCost) = 0;
};
class NowOrderFactory : public OrderFactory {
public:
Order* createOrder(const std::string& orderType, User* user, Restaurant* restaurant,
const std::vector<MenuItem>& items, PaymentStrategy* ps,
double totalCost) override {
Order* order = nullptr;
if (orderType == "Delivery") {
auto* dOrder = new DeliveryOrder();
dOrder->setUserAddress(user->getAddress());
order = dOrder;
} else {
auto* pOrder = new PickUpOrder();
pOrder->setRestaurantAddress(restaurant->getLocation());
order = pOrder;
}
order->setOrderDetails(user, restaurant, items, ps, totalCost, "Immediate (Now)");
return order;
}
};
class ScheduledOrderFactory : public OrderFactory {
private:
std::string scheduledSlot;
public:
ScheduledOrderFactory(std::string slot) : scheduledSlot(slot) {}
Order* createOrder(const std::string& orderType, User* user, Restaurant* restaurant,
const std::vector<MenuItem>& items, PaymentStrategy* ps,
double totalCost) override {
Order* order = nullptr;
if (orderType == "Delivery") {
auto* dOrder = new DeliveryOrder();
dOrder->setUserAddress(user->getAddress());
order = dOrder;
} else {
auto* pOrder = new PickUpOrder();
pOrder->setRestaurantAddress(restaurant->getLocation());
order = pOrder;
}
order->setOrderDetails(user, restaurant, items, ps, totalCost, scheduledSlot);
return order;
}
};
7. The Strategy Pattern for Dynamic Payments
Hardcoding payment gateways (e.g., embedding switch-cases for UPI vs Credit Card directly into Order) violates the Open/Closed Principle (OCP). By encapsulating algorithms inside a PaymentStrategy hierarchy, new payment rails (NetBanking, Wallets, Crypto) can be added without altering existing order classes.
class PaymentStrategy {
public:
virtual ~PaymentStrategy() {}
virtual bool pay(double amount) = 0;
};
class UPIPayment : public PaymentStrategy {
private:
std::string upiId;
public:
UPIPayment(std::string id) : upiId(id) {}
bool pay(double amount) override {
std::cout << "[Payment Gateway] Successfully debited INR " << amount
<< " using UPI ID: " << upiId << "\n";
return true;
}
};
class CreditCardPayment : public PaymentStrategy {
private:
std::string cardNumber;
public:
CreditCardPayment(std::string card) : cardNumber(card) {}
bool pay(double amount) override {
std::cout << "[Payment Gateway] Successfully charged INR " << amount
<< " to Card: " << cardNumber << "\n";
return true;
}
};
8. Notification Service & The Law of Demeter
A crucial architectural consideration arises during order placement: What parameters should helper services accept?
The Architectural Dilemma
Should NotificationService::notify() accept an Order* or individual unpacked primitives (userId, restaurantName, amount)?
- Principle of Least Knowledge (Law of Demeter): A module should not possess intimate knowledge of the inner traversal paths of third-party objects.
- Pragmatic Engineering Trade-Off: In an interview setting, passing the
Order*domain root directly tonotify(Order* order)is acceptable if the notification service is considered an internal domain consumer. However, unpackaging data at the orchestrator layer represents a cleaner, zero-leakage production approach.
class NotificationService {
public:
void notify(Order* order) {
if (!order) return;
std::cout << "\n================ NOTIFICATION ================\n"
<< "Alert: Order #" << order->getOrderId() << " placed successfully!\n"
<< "Type: " << order->getType() << "\n"
<< "Recipient: " << order->getUser()->getName() << "\n"
<< "Restaurant: " << order->getRestaurant()->getName() << "\n"
<< "Total Bill: INR " << order->getTotalCost() << "\n"
<< "Fulfillment Schedule: " << order->getScheduledTime() << "\n"
<< "==============================================\n\n";
}
};
9. The Orchestrator Pattern vs. Layered Architecture
To prevent client interfaces (CLI, Mobile Frontends, Web Frontends) from juggling 10 different subsystem classes, a facade/orchestration class (TomatoApp) acts as the single boundary point.
Architectural Critique: Single Responsibility Violation
TomatoApp coordinates searching, cart mutations, checkout validation, payments, and notifications.
- The Trade-Off: While convenient as an API gateway facade for interviews, it technically violates the Single Responsibility Principle (SRP) by aggregating too many operational concerns.
- Enterprise Alternative: In modern distributed frameworks (Spring Boot, NestJS, Go/gRPC), this monolithic orchestrator is split into a Controller Layer and modular Service Layer components (
SearchService,CartService,CheckoutService,PaymentService).
10. Complete Code Implementation
Below is the complete, runnable C++ implementation structuring all architectural layers discussed.
#include <iostream>
#include <string>
#include <vector>
// Forward Declarations
class MenuItem;
class Restaurant;
class Cart;
class User;
class Order;
// ==========================================
// 1. DOMAIN MODELS
// ==========================================
class MenuItem {
private:
std::string code;
std::string name;
double price;
public:
MenuItem(std::string c, std::string n, double p) : code(c), name(n), price(p) {}
std::string getCode() const { return code; }
std::string getName() const { return name; }
double getPrice() const { return price; }
};
class Restaurant {
private:
static int nextRestaurantId;
int id;
std::string name;
std::string location;
std::vector<MenuItem> menu;
public:
Restaurant(std::string n, std::string loc)
: id(++nextRestaurantId), name(n), location(loc) {}
void addMenuItem(const MenuItem& item) {
menu.push_back(item);
}
int getId() const { return id; }
std::string getName() const { return name; }
std::string getLocation() const { return location; }
const std::vector<MenuItem>& getMenu() const { return menu; }
};
int Restaurant::nextRestaurantId = 0;
class Cart {
private:
Restaurant* restaurant;
std::vector<MenuItem> items;
public:
Cart() : restaurant(nullptr) {}
void setRestaurant(Restaurant* r) { restaurant = r; }
Restaurant* getRestaurant() const { return restaurant; }
void addItem(const MenuItem& item) {
items.push_back(item);
}
void clear() {
items.clear();
restaurant = nullptr;
}
bool isEmpty() const { return items.empty(); }
double getTotalCost() const {
double sum = 0;
for (const auto& i : items) sum += i.getPrice();
return sum;
}
const std::vector<MenuItem>& getItems() const { return items; }
};
class User {
private:
int id;
std::string name;
std::string address;
Cart cart;
public:
User(int uid, std::string uname, std::string uaddr)
: id(uid), name(uname), address(uaddr) {}
int getId() const { return id; }
std::string getName() const { return name; }
std::string getAddress() const { return address; }
Cart* getCart() { return &cart; }
};
// ==========================================
// 2. STRATEGY PATTERN (PAYMENTS)
// ==========================================
class PaymentStrategy {
public:
virtual ~PaymentStrategy() {}
virtual bool pay(double amount) = 0;
};
class UPIPayment : public PaymentStrategy {
private:
std::string upiId;
public:
UPIPayment(std::string id) : upiId(id) {}
bool pay(double amount) override {
std::cout << "[Payment Gateway] Paid INR " << amount << " via UPI (" << upiId << ").\n";
return true;
}
};
class CreditCardPayment : public PaymentStrategy {
private:
std::string cardNumber;
public:
CreditCardPayment(std::string card) : cardNumber(card) {}
bool pay(double amount) override {
std::cout << "[Payment Gateway] Paid INR " << amount << " via Card (" << cardNumber << ").\n";
return true;
}
};
// ==========================================
// 3. POLYMORPHIC ORDERS & FACTORIES
// ==========================================
class Order {
protected:
static int nextOrderId;
int orderId;
User* user;
Restaurant* restaurant;
std::vector<MenuItem> items;
PaymentStrategy* paymentStrategy;
double totalCost;
std::string scheduledSlot;
public:
Order() : orderId(++nextOrderId), user(nullptr), restaurant(nullptr),
paymentStrategy(nullptr), totalCost(0.0) {}
virtual ~Order() {}
virtual std::string getType() const = 0;
void setOrderDetails(User* u, Restaurant* r, const std::vector<MenuItem>& itms,
PaymentStrategy* ps, double cost, const std::string& slot) {
user = u;
restaurant = r;
items = itms;
paymentStrategy = ps;
totalCost = cost;
scheduledSlot = slot;
}
bool executePayment() {
if (!paymentStrategy) return false;
return paymentStrategy->pay(totalCost);
}
int getOrderId() const { return orderId; }
User* getUser() const { return user; }
Restaurant* getRestaurant() const { return restaurant; }
const std::vector<MenuItem>& getItems() const { return items; }
double getTotalCost() const { return totalCost; }
std::string getScheduledTime() const { return scheduledSlot; }
};
int Order::nextOrderId = 0;
class DeliveryOrder : public Order {
private:
std::string deliveryAddress;
public:
std::string getType() const override { return "Delivery"; }
void setDeliveryAddress(const std::string& addr) { deliveryAddress = addr; }
std::string getDeliveryAddress() const { return deliveryAddress; }
};
class PickUpOrder : public Order {
private:
std::string restaurantAddress;
public:
std::string getType() const override { return "PickUp"; }
void setRestaurantAddress(const std::string& addr) { restaurantAddress = addr; }
std::string getRestaurantAddress() const { return restaurantAddress; }
};
class OrderFactory {
public:
virtual ~OrderFactory() {}
virtual Order* createOrder(const std::string& type, User* u, Restaurant* r,
const std::vector<MenuItem>& itms, PaymentStrategy* ps,
double cost) = 0;
};
class NowOrderFactory : public OrderFactory {
public:
Order* createOrder(const std::string& type, User* u, Restaurant* r,
const std::vector<MenuItem>& itms, PaymentStrategy* ps,
double cost) override {
Order* order = nullptr;
if (type == "Delivery") {
auto* dOrder = new DeliveryOrder();
dOrder->setDeliveryAddress(u->getAddress());
order = dOrder;
} else {
auto* pOrder = new PickUpOrder();
pOrder->setRestaurantAddress(r->getLocation());
order = pOrder;
}
order->setOrderDetails(u, r, itms, ps, cost, "Now (Immediate)");
return order;
}
};
class ScheduledOrderFactory : public OrderFactory {
private:
std::string timeSlot;
public:
ScheduledOrderFactory(std::string slot) : timeSlot(slot) {}
Order* createOrder(const std::string& type, User* u, Restaurant* r,
const std::vector<MenuItem>& itms, PaymentStrategy* ps,
double cost) override {
Order* order = nullptr;
if (type == "Delivery") {
auto* dOrder = new DeliveryOrder();
dOrder->setDeliveryAddress(u->getAddress());
order = dOrder;
} else {
auto* pOrder = new PickUpOrder();
pOrder->setRestaurantAddress(r->getLocation());
order = pOrder;
}
order->setOrderDetails(u, r, itms, ps, cost, timeSlot);
return order;
}
};
// ==========================================
// 4. MANAGERS (SINGLETONS) & SERVICES
// ==========================================
class RestaurantManager {
private:
std::vector<Restaurant*> restaurants;
static RestaurantManager* instance;
RestaurantManager() {}
public:
static RestaurantManager* getInstance() {
if (!instance) instance = new RestaurantManager();
return instance;
}
void addRestaurant(Restaurant* r) { restaurants.push_back(r); }
std::vector<Restaurant*> searchByLocation(const std::string& loc) {
std::vector<Restaurant*> matches;
for (auto* r : restaurants) {
if (r->getLocation() == loc) matches.push_back(r);
}
return matches;
}
};
RestaurantManager* RestaurantManager::instance = nullptr;
class OrderManager {
private:
std::vector<Order*> orderHistory;
static OrderManager* instance;
OrderManager() {}
public:
static OrderManager* getInstance() {
if (!instance) instance = new OrderManager();
return instance;
}
void addOrder(Order* o) { orderHistory.push_back(o); }
void listOrders() const {
std::cout << "[Order Manager Registry] Total historical orders: "
<< orderHistory.size() << "\n";
}
};
OrderManager* OrderManager::instance = nullptr;
class NotificationService {
public:
void notify(Order* order) {
if (!order) return;
std::cout << "\n================ NOTIFICATION SYSTEM ================\n"
<< "Success! Placed " << order->getType() << " Order #" << order->getOrderId() << "\n"
<< "Customer : " << order->getUser()->getName() << "\n"
<< "Restaurant : " << order->getRestaurant()->getName() << "\n"
<< "Total Bill : INR " << order->getTotalCost() << "\n"
<< "Slot : " << order->getScheduledTime() << "\n"
<< "=====================================================\n\n";
}
};
// ==========================================
// 5. APPLICATION FACADE / ORCHESTRATOR
// ==========================================
class TomatoApp {
private:
RestaurantManager* restaurantManager;
OrderManager* orderManager;
NotificationService notificationService;
void seedRestaurants() {
auto* r1 = new Restaurant("Haldiram", "Delhi");
r1->addMenuItem(MenuItem("H1", "Chole Bhature", 140.0));
r1->addMenuItem(MenuItem("H2", "Rajbhog", 50.0));
auto* r2 = new Restaurant("Bikanervala", "Delhi");
r2->addMenuItem(MenuItem("B1", "Samosa", 20.0));
r2->addMenuItem(MenuItem("B2", "Kachori", 30.0));
auto* r3 = new Restaurant("Paradise", "Hyderabad");
r3->addMenuItem(MenuItem("P1", "Chicken Biryani", 250.0));
restaurantManager->addRestaurant(r1);
restaurantManager->addRestaurant(r2);
restaurantManager->addRestaurant(r3);
}
public:
TomatoApp() {
restaurantManager = RestaurantManager::getInstance();
orderManager = OrderManager::getInstance();
seedRestaurants();
}
std::vector<Restaurant*> searchRestaurants(const std::string& location) {
return restaurantManager->searchByLocation(location);
}
bool selectRestaurant(User* user, Restaurant* restaurant) {
if (!user || !restaurant) return false;
user->getCart()->clear();
user->getCart()->setRestaurant(restaurant);
return true;
}
bool addToCart(User* user, const std::string& itemCode) {
Cart* cart = user->getCart();
Restaurant* res = cart->getRestaurant();
if (!res) {
std::cout << "Error: Must select a restaurant before adding items.\n";
return false;
}
for (const auto& item : res->getMenu()) {
if (item.getCode() == itemCode) {
cart->addItem(item);
std::cout << "[Cart] Added " << item.getName() << " to cart.\n";
return true;
}
}
std::cout << "Item code not found in current restaurant menu.\n";
return false;
}
Order* checkout(User* user, OrderFactory* factory, const std::string& orderType, PaymentStrategy* payment) {
Cart* cart = user->getCart();
if (cart->isEmpty()) {
std::cout << "Error: Cart is empty. Checkout aborted.\n";
return nullptr;
}
Restaurant* res = cart->getRestaurant();
double totalCost = cart->getTotalCost();
// Delegate construction to factory
Order* order = factory->createOrder(orderType, user, res, cart->getItems(), payment, totalCost);
return order;
}
bool processOrderPayment(Order* order) {
if (!order) return false;
bool isSuccess = order->executePayment();
if (isSuccess) {
orderManager->addOrder(order);
notificationService.notify(order);
order->getUser()->getCart()->clear(); // Flush cart on successful placement
}
return isSuccess;
}
};
// ==========================================
// 6. MAIN EXECUTION WORKFLOW
// ==========================================
int main() {
TomatoApp app;
std::cout << "--- 1. User Onboarding ---\n";
User customer(101, "Aditya Sharma", "Connaught Place, New Delhi");
std::cout << "Active user: " << customer.getName() << " residing at " << customer.getAddress() << "\n\n";
std::cout << "--- 2. Search Restaurants by Location ---\n";
auto nearby = app.searchRestaurants("Delhi");
if (nearby.empty()) {
std::cout << "No restaurants found.\n";
return 0;
}
std::cout << "Found " << nearby.size() << " restaurant(s) in Delhi:\n";
for (auto* r : nearby) {
std::cout << " -> " << r->getName() << " (ID: " << r->getId() << ")\n";
}
std::cout << "\n--- 3. Select Restaurant & Populate Cart ---\n";
Restaurant* selectedRestaurant = nearby[0]; // Select "Haldiram"
app.selectRestaurant(&customer, selectedRestaurant);
std::cout << "Selected: " << selectedRestaurant->getName() << "\n";
app.addToCart(&customer, "H1"); // Chole Bhature
app.addToCart(&customer, "H2"); // Rajbhog
std::cout << "Current Cart Total: INR " << customer.getCart()->getTotalCost() << "\n\n";
std::cout << "--- 4. Configure Checkout Parameters ---\n";
// Using NowOrderFactory and UPIPayment Strategy
NowOrderFactory nowFactory;
UPIPayment upiGateway("aditya@okaxis");
Order* placedOrder = app.checkout(&customer, &nowFactory, "Delivery", &upiGateway);
std::cout << "\n--- 5. Payment & Fulfillment Processing ---\n";
if (placedOrder) {
app.processOrderPayment(placedOrder);
}
std::cout << "Checking Cart status after order: "
<< (customer.getCart()->isEmpty() ? "Empty (Cleaned)" : "Stale items present") << "\n";
delete placedOrder;
return 0;
}
11. Production Extensibility & Practical Pitfalls
When concluding an LLD discussion, proposing meaningful architectural extensions demonstrates seniority beyond basic class syntax.
1. The Strategy Factory Enhancement
In standard systems, the client should not manually instantiate concrete strategies (new UPIPayment()) inside the UI controller. A PaymentStrategyFactory centralizes construction:
2. Notification Dispatcher Hierarchy
Instead of a single procedural print service, NotificationService can implement an observer/strategy pattern supporting multiple distribution channels:
3. Layered Web Architecture (De-coupling the Orchestrator)
In enterprise frameworks, the TomatoApp God-Class is separated into clean, modular tiers:
12. Summary and Key Takeaways
- Scoping Controls Interview Time: Distinguishing User-Centric operations from Delivery-Agent mechanics keeps the low-level implementation realistic for a single session.
- Aggregation vs. Composition: A
Cartholds an independentRestaurantvia Aggregation, but menu items belong directly to aRestaurantcatalog via Composition. - Singleton Isolation: In-memory registries (
RestaurantManager,OrderManager) must be Singletons to prevent split-brain state anomalies across the application lifecycle. - Factory Method Isolates Order Scheduling: Separating order construction (
NowOrderFactoryvsScheduledOrderFactory) prevents lifecycle attributes (immediate fulfillment vs. reservation windows) from polluting client checkouts. - Strategy Pattern Safeguards OCP: Encapsulating billing gateways (
UPIPayment,CreditCardPayment) behind a common interface allows new payment methods to be added without modifying order models. - Law of Demeter (Least Knowledge): Domain factories should only receive the data they need to build their target objects, avoiding deep traversals across nested object graphs.
- Facade Orchestration: While an orchestrator like
TomatoAppsimplifies interactions for the client, real-world systems separate this orchestration into decoupled Controller, Service, and Repository layers.