A full four-pattern capstone — State (order lifecycle) + Strategy (pricing/matching) + Observer (notifications) + Factory (notification channel creation) — building the complete class diagram in one sitting and justifying every pattern choice.
Published September 23, 2026
Multi-Pattern Problem: Design a Food Ordering Class Model (earlier in this chapter) deliberately scoped down to TWO patterns (State + Observer) to practice their interaction in isolation. This capstone is the full version — FOUR patterns combined — matching the real complexity of Design a Food Delivery Platform's system-level design at the CLASS level.
interface OrderState {
OrderState next(FoodOrder order);
String name();
}
class PlacedState implements OrderState {
public OrderState next(FoodOrder order) { return new ConfirmedState(); }
public String name() { return "PLACED"; }
}
// ... ConfirmedState, PreparingState, OutForDeliveryState, DeliveredState, similarly
This reuses OMS — State Machine Design's exact discipline (explicit transition modeling, each state knowing its own valid next states) — an order moves through PLACED → CONFIRMED → PREPARING → OUT_FOR_DELIVERY → DELIVERED, each transition triggering the notification and pricing concerns below.
interface PricingStrategy { BigDecimal calculatePrice(FoodOrder order); }
class SurgePricingStrategy implements PricingStrategy {
public BigDecimal calculatePrice(FoodOrder order) { return order.basePrice().multiply(currentSurgeMultiplier()); }
}
interface MatchingStrategy { DeliveryPartner findPartner(FoodOrder order); }
class NearestAvailableMatchingStrategy implements MatchingStrategy {
public DeliveryPartner findPartner(FoodOrder order) { /* geospatial lookup, per Uber/Food Delivery Platform */ }
}
Both pricing and partner-matching are genuinely SEPARATE, independently-varying concerns — pricing might switch between flat-rate and surge-pricing strategies based on demand; matching might switch algorithms as covered in Food Delivery Platform's joint prep-time/travel-time optimization. Keeping each as its own Strategy interface means the order-processing flow calls pricingStrategy.calculatePrice(order) and matchingStrategy.findPartner(order) without ever branching on WHICH specific strategy is active — new pricing or matching logic is purely additive.
interface OrderEventListener { void onStateChange(FoodOrder order, OrderState oldState, OrderState newState); }
class CustomerNotifier implements OrderEventListener {
public void onStateChange(FoodOrder order, OrderState oldState, OrderState newState) {
notificationService.send(order.customerId(), buildMessage(newState));
}
}
class RestaurantNotifier implements OrderEventListener { /* similarly, notifies the restaurant */ }
Every state transition potentially needs to notify MULTIPLE independent parties (the customer, the restaurant, possibly the delivery partner) — the same Observer shape as Design an Audit Logging Framework's multi-sink dispatch, applied here to order-state notifications instead of audit records. FoodOrder's transition logic stays unaware of how many listeners exist or what they each do — it just fires the event.
interface NotificationChannel { void send(String recipientId, String message); }
class NotificationChannelFactory {
NotificationChannel createChannel(ChannelType type) {
return switch (type) {
case PUSH -> new PushNotificationChannel();
case SMS -> new SmsNotificationChannel();
case EMAIL -> new EmailNotificationChannel();
};
}
}
This directly reuses Notification Service's own Strategy-per-channel design (NotificationDispatcher/NotificationChannel), adding a Factory specifically for the CREATION step — CustomerNotifier doesn't need to know HOW to construct a PushNotificationChannel vs an SmsNotificationChannel; it asks the factory for "whatever channel type this customer prefers," keeping channel-CONSTRUCTION logic centralized and separate from channel-USAGE logic.
The actual exercise here isn't just implementing each pattern in isolation — it's producing ONE coherent class diagram where all four interact correctly: an OrderState transition fires OrderEventListener notifications (Observer), which use channels built by NotificationChannelFactory (Factory), while PricingStrategy and MatchingStrategy (Strategy) are invoked at specific points in that same state-transition flow (typically PLACED→CONFIRMED triggering matching, and price calculation happening at PLACED). Justifying EACH pattern choice explicitly — why State here and not a plain enum, why Strategy here and not an if/else — is the actual interview-relevant skill; a correct diagram with no justification reads as pattern-matching from memory, not genuine design reasoning.
Q: Why use the State pattern here instead of a simple enum with a switch statement for transitions?
A: The State pattern puts transition LOGIC inside each state class itself (PlacedState.next() knows it goes to ConfirmedState), keeping the rules co-located with the state they apply to; a plain enum with a giant switch statement centralizes ALL transition logic in one place, which becomes harder to extend safely as more states are added — this is the same OCP argument made for State pattern throughout the Behavioral Design Patterns chapter.
Q: Could Strategy and Factory be considered redundant here, given both involve 'choosing an implementation'? A: They solve genuinely different problems — Strategy is about choosing BEHAVIOR (which algorithm to run, e.g. which pricing calculation), while Factory is about choosing CONSTRUCTION (which concrete object to instantiate) — a Factory could very reasonably be used to CREATE a chosen Strategy implementation, and the two patterns compose naturally rather than compete for the same job.
Q: How does this class-level design relate to Design a Food Delivery Platform's system-level design? A: They operate at different levels of the same problem — this capstone's classes would live INSIDE one service (likely the Order Service) in that system-level design; the class-level State/Strategy/Observer/Factory patterns here don't replace or conflict with the system-level geospatial matching, ETA estimation, or three-sided coordination concerns covered there — they're the internal implementation detail of how one piece of that larger system is actually built.
Q: What's a realistic time budget for building this full four-pattern diagram in one sitting, per the exercise's own framing? A: Meaningfully longer than any single-pattern exercise in this chapter — given the genuine interaction complexity across four patterns, budgeting significantly more time than the 25-45 minutes typical of a single-topic Machine Coding exercise is realistic, and treating this as a genuine capstone (drawing on everything else in this chapter) rather than a timed drill is the more productive framing.