Cash-on-Delivery Refunds & Fat Partner Interfaces
Our monolithic `RestaurantPartner` interface forces cloud kitchens to implement `reserveDineInTable()` and `printValetTicket()` by throwing dummy runtime exceptions (**ISP violation**). Similarly, a generic `PaymentMethod` interface forces `CashOnDeliveryPayment` to throw on `refundOnline()`, causing automated refund batch jobs to crash in production (**LSP violation**).
CashOnDeliveryPayment inherits refundOnline() and throws std::runtime_error. Callers iterating PaymentMethod* expect every instance to honour the interface contract β violating the Liskov Substitution Principle (LSP).
Subtypes must be substitutable for their base types without throwing unsupported exceptions (LSP); interfaces must be small and client-specific rather than fat and general-purpose (ISP).
We decompose fat interfaces into granular role interfaces: Payable and OnlineRefundable for payments; DeliveryPartner, DineInReservable, and ValetService for restaurants. CardPayment and UpiPayment implement both Payable and OnlineRefundable. CashOnDeliveryPayment implements only Payable. The batch refund processor accepts OnlineRefundable&, turning invalid refund attempts into compile-time errors.
In Sprint 6 (A6), we inverted dependencies in OrderService using constructor injection so high-level checkout logic depends only on abstract repository and gateway interfaces.
We are expanding our catalog to support diverse restaurant business models (delivery-only cloud kitchens alongside full-service dine-in restaurants) and multiple payment settlement types (including Cash on Delivery).
Our monolithic RestaurantPartner interface forces cloud kitchens to implement reserveDineInTable() and printValetTicket() by throwing dummy runtime exceptions (ISP violation). Similarly, a generic PaymentMethod interface forces CashOnDeliveryPayment to throw on refundOnline(), causing automated refund batch jobs to crash in production (LSP violation).
Decomposing fat interfaces into lean, focused role interfaces (DeliveryPartner, DineInReservable, Payable, OnlineRefundable) turns invalid operations into compile-time errors. Next in Sprint 8 (A8 Β· Adapter), we onboard Bella Pizza and 3rd-party providers whose legacy C-style SDKs (expecting raw C-strings and paise integers like LegacyPizzaSdk / StripeIndiaSDK) fail to match our clean domain interfaces.
The product ticket
Two critical production defects were reported this week: (1) An automated refund cron job looped over orders calling paymentMethod->refundOnline(), but CashOnDelivery orders threw std::runtime_error("COD cannot refund online"), crashing the batch processor. (2) A new delivery-only cloud kitchen brand onboarded, but was forced to write dummy stubs throwing exceptions for reserveDineInTable() and printValetTicket() because RestaurantPartner was a bloated fat interface.
- β’
Ensure all subtypes honour base contracts without throwing 'not supported' exceptions, fulfilling the Liskov Substitution Principle (LSP).
- β’
Segregate bloated interfaces into focused, cohesive role interfaces so implementers never depend on methods they do not use (Interface Segregation Principle (ISP)).
- β’
Catch invalid operations (like attempting to refund Cash-on-Delivery online) at compile time rather than during production runtime crashes.
Product wants online refunds to report domain failure reasons (e.g. 'refund window expired', 'exceeds transaction amount') without using expensive C++ exceptions for standard control flow.
Step 1 β The naive code & why it breaks
A monolithic PaymentMethod interface forces CashOnDeliveryPayment to throw on refundOnline(), while a bloated RestaurantPartner forces CloudKitchen to implement dine-in and valet methods.
#include <iostream>
#include <memory>
#include <print>
#include <stdexcept>
#include <string>
#include <vector>
// LSP Violation: PaymentMethod assumes all payment methods can refund online.
class PaymentMethod {
public:
virtual ~PaymentMethod() = default;
virtual void pay(int amountRupees) = 0;
virtual void refundOnline(int amountRupees) = 0;
};
class CardPayment : public PaymentMethod {
std::string cardMasked_{"*4242"};
public:
void pay(int amountRupees) override {
std::println("[CARD] Paid Rs.{} using card {}", amountRupees, cardMasked_);
}
void refundOnline(int amountRupees) override {
std::println("[CARD] Refunded Rs.{} back to card {}", amountRupees, cardMasked_);
}
};
class CashOnDeliveryPayment : public PaymentMethod {
public:
void pay(int amountRupees) override {
std::println("[COD] Paid Rs.{} in physical cash to delivery driver", amountRupees);
}
// LSP Violation: violates base class contract by throwing an exception!
void refundOnline(int) override {
throw std::runtime_error("COD orders cannot be refunded online! Cash refund required.");
}
};
// ISP Violation: Bloated fat interface forces all restaurants to support dine-in and valet!
class RestaurantPartner {
public:
virtual ~RestaurantPartner() = default;
virtual void acceptDeliveryOrder(const std::string& orderId) = 0;
virtual void reserveDineInTable(int partySize, const std::string& time) = 0;
virtual void printValetTicket(const std::string& vehicleNumber) = 0;
};
class CloudKitchen : public RestaurantPartner {
public:
void acceptDeliveryOrder(const std::string& orderId) override {
std::println("[CloudKitchen] Cooking delivery order {}", orderId);
}
// ISP Violation: dummy stubs forced upon delivery-only cloud kitchen!
void reserveDineInTable(int, const std::string&) override {
throw std::logic_error("Cloud kitchen has no dine-in seating!");
}
void printValetTicket(const std::string&) override {
throw std::logic_error("Cloud kitchen has no valet parking!");
}
};
int main() {
std::println("--- Naive Demo: Demonstrating LSP & ISP Fragility ---");
CardPayment card;
card.pay(500);
card.refundOnline(500);
CashOnDeliveryPayment cod;
cod.pay(300);
try {
cod.refundOnline(300); // Throws!
} catch (const std::exception& e) {
std::println("Caught expected LSP crash: {}", e.what());
}
CloudKitchen kitchen;
kitchen.acceptDeliveryOrder("ORD-901");
try {
kitchen.reserveDineInTable(4, "19:00"); // Throws!
} catch (const std::exception& e) {
std::println("Caught expected ISP crash: {}", e.what());
}
return 0;
}CashOnDeliveryPayment inherits refundOnline() and throws std::runtime_error. Callers iterating PaymentMethod* expect every instance to honour the interface contract β violating the Liskov Substitution Principle (LSP).
RestaurantPartner bundles delivery, dine-in table reservations, and valet ticketing into one fat interface. CloudKitchen is forced to implement irrelevant methods that throw std::logic_error β violating the Interface Segregation Principle (ISP).
Because CashOnDeliveryPayment is a PaymentMethod, the compiler cannot stop callers from passing it to online refund routines. The bug is only discovered when the production server crashes.
Step 2 β The fix: Liskov Substitution Principle (LSP) + Interface Segregation Principle (ISP)
Subtypes must be substitutable for their base types without throwing unsupported exceptions (LSP); interfaces must be small and client-specific rather than fat and general-purpose (ISP).
We decompose fat interfaces into granular role interfaces: Payable and OnlineRefundable for payments; DeliveryPartner, DineInReservable, and ValetService for restaurants. CardPayment and UpiPayment implement both Payable and OnlineRefundable. CashOnDeliveryPayment implements only Payable. The batch refund processor accepts OnlineRefundable&, turning invalid refund attempts into compile-time errors.
Payable β declares virtual void pay(int amountRupees) = 0; for methods that can accept payments.
OnlineRefundable β declares virtual bool refundOnline(int amountRupees) = 0; for methods capable of reverse electronic transfer.
DeliveryPartner β declares virtual void acceptDeliveryOrder(string_view) = 0; for food preparation and packing.
DineInReservable β declares virtual void reserveDineInTable(int, string_view) = 0; for on-premise dining rooms.
DineInRestaurant β implements DeliveryPartner, DineInReservable, and ValetService without dummy methods.
CloudKitchen β implements only DeliveryPartner cleanly.
Unlock Full UML Diagram & C++23 Solution
The interactive UML architecture diagrams, refactored C++23 code, real-world extensions, and interview Q&A are part of Brute2Best Pro. All 7 foundation chapters are 100% free.
First anchor question per pattern, Part 1 LLD & Module 1 C++ basics are 100% Free forever.