Bird
Raised Fist0

Design a Ride Booking Class Hierarchy - Driver, Rider, Trip, Vehicle, Fare Calculation

LLDmediumInterview freq: high
Choose your preparation mode4 modes available
๐ŸŽฏ
Ride Booking Class Hierarchy Design
40 min ยท medium
medium

Design a ride booking system that manages Drivers, Riders, Trips, Vehicles, and Fare Calculation. The system must handle multiple vehicle types, concurrent trip bookings, and dynamic fare calculation based on distance and time.

  • Drivers can have multiple Vehicles but only one active Vehicle per trip.
  • Riders can request Trips which are assigned to available Drivers.
  • Fare calculation depends on distance traveled and time taken with surge pricing.
  • Trip status must be thread-safe to prevent double booking or inconsistent state.
  • Vehicle types include CAR, BIKE, and AUTO with different fare multipliers.
๐Ÿ“‹
Guided Steps
1/5
Step 1
Define Vehicle Hierarchy and Types
Design the Vehicle class hierarchy to represent different vehicle types with shared state and behavior.
๐Ÿ’ก Hint
Use an abstract class for Vehicle to hold common fields like licensePlate and vehicleType. Use enum for VehicleType to avoid string typos.
abstract class Vehicle with fields licensePlate (String), vehicleType (enum VehicleType) - abstract because it holds shared instance STATE; interface cannot hold fields in Java
Mid: Recognizes abstract class for shared state and enum for type safety. Senior: Explains why interface cannot hold instance fields and how enum prevents runtime errors from string typos.
โš ๏ธCommon Wrong Answer
Using interface Vehicle with default methods and String vehicleType leads to inability to hold shared state and runtime bugs from typos.
Step 2
Model Driver and Rider Entities
Design Driver and Rider classes with appropriate relationships to Vehicles and Trips.
๐Ÿ’ก Hint
Use composition for Driver owns Vehicles (lifecycle bound), aggregation for Rider to Trips (independent lifecycle). Use synchronized methods to update Driver availability.
Driver class has synchronized boolean isAvailable() and synchronized void assignTrip(Trip t) to atomically update availability; composition relationship with Vehicles (Driver owns Vehicles lifecycle); Rider aggregates Trips (Trips can exist independently).
Mid: Identifies composition vs aggregation relationships. Senior: Explains synchronized methods prevent race conditions when multiple threads assign trips concurrently.
โš ๏ธCommon Wrong Answer
Using non-synchronized methods to update Driver availability causes race conditions leading to double booking.
Step 3
Design Trip Lifecycle and Thread Safety
Implement Trip class with thread-safe status updates to prevent concurrent state corruption.
๐Ÿ’ก Hint
Use volatile TripStatus field and synchronized methods to atomically check and update status. TripStatus as enum ensures exhaustive switch handling.
volatile TripStatus status; public synchronized boolean startTrip() atomically checks status == REQUESTED and sets to ONGOING; enum TripStatus {REQUESTED, ONGOING, COMPLETED, CANCELLED} for type safety.
Mid: Uses enum for TripStatus and synchronized methods for state changes. Senior: Traces thread interleaving where without volatile and synchronized, two threads can start the same trip causing inconsistent state.
โš ๏ธCommon Wrong Answer
Using String for status and no synchronization leads to stale reads and race conditions causing double trip starts.
Step 4
Implement Fare Calculation Strategy
Design a flexible fare calculation mechanism supporting different vehicle types and surge pricing.
๐Ÿ’ก Hint
Use Strategy pattern with FareCalculator interface and concrete classes per vehicle type. Use enum VehicleType in switch for fare multipliers. FareCalculator implementations hold no state, so interface suffices.
FareCalculator interface with method double calculateFare(double distanceKm, double timeMinutes, double surgeMultiplier); concrete classes CarFareCalculator, BikeFareCalculator implement it; VehicleType enum used in factory method to select FareCalculator.
Mid: Recognizes Strategy pattern for fare calculation. Senior: Explains why interface is used here (no instance state) and how enum switch ensures compile-time exhaustiveness.
โš ๏ธCommon Wrong Answer
Hardcoding fare logic in Vehicle class violates Single Responsibility Principle and makes extension difficult.
Step 5
Handle Concurrent Trip Booking
Ensure that multiple concurrent trip requests do not assign the same Driver or Vehicle simultaneously.
๐Ÿ’ก Hint
Use synchronized methods on Driver to atomically check availability and assign trip. Use volatile boolean isAvailable to ensure visibility across threads.
volatile boolean isAvailable in Driver; public synchronized boolean assignTripIfAvailable(Trip t) atomically checks isAvailable and assigns trip, sets isAvailable=false; prevents double booking under concurrent threads.
Mid: Uses synchronized assignTripIfAvailable method. Senior: Explains thread interleaving where without volatile and synchronized, two threads read isAvailable=true and both assign trips causing double booking.
โš ๏ธCommon Wrong Answer
Using non-volatile isAvailable and no synchronization leads to visibility issues and race conditions causing double booking.
๐Ÿ”Œ
API Design Challenge
Design APIs to support ride booking, trip management, and fare calculation with concurrency control.
  • Use synchronized methods and volatile fields to ensure thread safety in trip and driver assignment.
  • Design RESTful endpoints for Riders to request trips and Drivers to update trip status.
  • Use enum types in API payloads to enforce type safety and reduce errors.
๐Ÿ“Š
Scoring Rubric
โ–ผ Show
Must Have - 60%
  • abstract class Vehicle with shared state licensePlate and vehicleType
  • enum VehicleType and TripStatus for type safety
  • synchronized methods for atomic state updates in Driver and Trip
  • volatile fields for visibility in concurrent environment
  • Strategy pattern for fare calculation with FareCalculator interface
Good to Have - 30%
  • composition relationship between Driver and Vehicles with lifecycle ownership
  • aggregation relationship between Rider and Trips
  • Factory method selecting FareCalculator based on VehicleType enum
  • exhaustive switch statements on enums
  • clear separation of concerns respecting SOLID principles
Bonus - 10%
  • explanation of thread interleaving scenarios causing race conditions
  • use of synchronized boolean assignTripIfAvailable to prevent double booking
  • discussion of why interface cannot hold instance state in Java
  • use of volatile to prevent stale reads in concurrent threads
  • extensibility for new vehicle types and fare rules without modifying existing classes
โœ…
Model Answer
โ–ผ Reveal

This design models a ride booking system with a clear class hierarchy and concurrency controls. Vehicles are represented by an abstract class with an enum VehicleType for type safety. Drivers own Vehicles via composition and manage availability with synchronized methods and a volatile flag. Riders aggregate Trips which have a thread-safe lifecycle managed by volatile TripStatus and synchronized state transitions. Fare calculation uses the Strategy pattern with a FareCalculator interface and concrete implementations per vehicle type, selected via a factory method using VehicleType enum. The design ensures thread safety, extensibility, and adherence to SOLID principles.

  • Use abstract class Vehicle to hold shared instance state (licensePlate, vehicleType) because interfaces cannot hold fields in Java.
  • Use enum VehicleType and TripStatus to prevent runtime bugs from string typos and enable exhaustive switch statements.
  • Driver uses composition to own Vehicles, ensuring lifecycle management; Rider aggregates Trips allowing independent lifecycle.
  • Synchronize methods in Driver and Trip to ensure atomic check-and-set operations preventing race conditions.
  • Declare volatile fields (isAvailable in Driver, status in Trip) to guarantee visibility of state changes across threads.
  • Implement FareCalculator interface with no instance state and concrete classes per vehicle type following Strategy pattern.
  • Use a factory method with VehicleType enum to select the appropriate FareCalculator implementation.
  • Manage Trip lifecycle with synchronized methods and volatile status to prevent inconsistent concurrent state.
  • Avoid hardcoding fare logic in entities to respect Single Responsibility Principle and enable easy extension.
  • Use synchronized boolean assignTripIfAvailable(Trip t) method to atomically assign trips and update availability, preventing double booking.
  • abstract class Vehicle holds shared instance state licensePlate and vehicleType; interface cannot hold fields in Java
  • enum VehicleType and TripStatus prevent runtime bugs from string typos and enable exhaustive switch statements
  • Driver uses composition to own Vehicles; Rider aggregates Trips with independent lifecycle
  • synchronized methods in Driver and Trip ensure atomic check-and-set operations preventing race conditions
  • volatile fields (e.g., isAvailable, TripStatus) guarantee visibility of state changes across threads
  • Strategy pattern with FareCalculator interface enables flexible fare calculation without state, suitable for interface use
  • Factory method uses VehicleType enum to select appropriate FareCalculator implementation
  • Trip lifecycle managed with synchronized methods and volatile status to prevent inconsistent concurrent state
  • Avoid hardcoding fare logic in entities to respect Single Responsibility Principle
  • Use synchronized boolean assignTripIfAvailable(Trip t) to atomically assign trips and update availability

Practice

(1/5)
1. In the Car Rental System, the BookingManager class has a private constructor and a static synchronized getInstance() method. Which design pattern is applied here, and why is this the correct choice for managing bookings?
easy
A. Singleton pattern, to ensure only one BookingManager handles all bookings globally.
B. Prototype pattern, to clone BookingManager instances for concurrent booking handling.
C. Builder pattern, to construct complex BookingManager objects step-by-step.
D. Factory pattern, because it creates BookingManager instances on demand.

Solution

  1. Step 1: Identify the pattern from constructor and method

    A private constructor prevents external instantiation, and a static synchronized getInstance() method ensures a single shared instance, matching the Singleton pattern.
  2. Step 2: Reason why Singleton fits BookingManager

    BookingManager must coordinate all bookings centrally to avoid conflicts; multiple instances would cause inconsistent state, so Singleton ensures global coordination.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Private constructor + static synchronized getInstance() -> Singleton [check]
Hint: Private constructor + static getInstance() -> Singleton [check]
Common Mistakes:
2. A class SecureImageProxy controls access to RealImage by checking user permissions before loading the image data lazily. Which design pattern is this an example of, and why is it the correct choice here?
easy
A. Proxy pattern, because it controls access and delays expensive object creation until needed.
B. Decorator pattern, because it adds responsibilities dynamically without changing the original object.
C. Adapter pattern, because it converts the interface of RealImage to a different one.
D. Facade pattern, because it provides a simplified interface to a complex subsystem.

Solution

  1. Step 1: Identify the role of SecureImageProxy

    This class controls access to RealImage and delays loading until necessary, matching Proxy's intent.
  2. Step 2: Eliminate other patterns

    Decorator adds behavior without access control; Adapter changes interface; Facade simplifies interface but doesn't control access or lazy load.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Proxy controls access and lazy loads expensive objects [check]
Hint: Proxy controls access and lazy loads objects [check]
Common Mistakes:
3. In a UML class diagram, should Vehicle be modeled as an abstract class or an interface if it has state like speed and behavior like accelerate()? Why?
easy
A. Interface, because it allows multiple inheritance and defines behavior without state.
B. Abstract class, because it can hold shared state and provide default behavior implementations.
C. Interface, because state can be stored externally and behavior defined via default methods.
D. Abstract class, because interfaces cannot declare methods with parameters.

Solution

  1. Step 1: Analyze state and behavior requirements

    Vehicle has state (speed) and behavior (accelerate()), so it needs a construct that supports instance variables and method implementations.
  2. Step 2: Evaluate abstract class vs interface

    Abstract classes can hold state and provide default behavior; interfaces cannot hold instance state (only constants) and default methods can't maintain state.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    State + behavior with shared code -> abstract class [check]
Hint: Stateful base class -> abstract class, not interface [check]
Common Mistakes:
4. In a concurrent Job Scheduler, if the scheduleJob() method is not synchronized, which exact thread interleaving causes the same Job to be scheduled twice?
medium
A. Thread 1 and Thread 2 both call scheduleJob() simultaneously and both succeed without checks.
B. Thread 1 adds job, then Thread 2 removes it before completion, causing inconsistency.
C. Thread 1 checks if job exists -> false, context switch; Thread 2 checks -> false; both add job concurrently.
D. Thread 1 waits for Thread 2 to finish scheduling, causing deadlock.

Solution

  1. Step 1: Identify the check-then-act race condition

    Both threads check if the job is already scheduled before adding. Without synchronization, both see job absent.
  2. Step 2: Trace thread interleaving

    Thread 1 checks -> false, context switch before adding; Thread 2 checks -> false, adds job; Thread 1 resumes and adds job again, causing double scheduling.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Check-then-act gap without synchronization -> double scheduling [check]
Hint: Check-then-act gap -> double scheduling race [check]
Common Mistakes:
5. You want to add a WeekendFeeStrategy to calculate weekend parking fees without modifying existing classes. Which SOLID principle guides the minimum change, and what is the correct approach?
medium
A. Modify the ParkingLot class to add weekend fee logic, enforcing Single Responsibility Principle.
B. Add weekend fee logic inside existing FeeStrategy implementations, following Dependency Inversion Principle.
C. Create a WeekendFeeStrategy subclass of ParkingLot to override fee calculation, following Liskov Substitution Principle.
D. Create a new WeekendFeeStrategy class implementing FeeStrategy interface, and inject it where needed, following Open/Closed Principle.

Solution

  1. Step 1: Identify the SOLID principle for extensibility

    Open/Closed Principle (OCP) requires adding new functionality without modifying existing code.
  2. Step 2: Evaluate approaches

    Creating a new FeeStrategy implementation and injecting it respects OCP and DIP; modifying ParkingLot or existing strategies violates OCP or mixes responsibilities.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    New FeeStrategy class + injection -> OCP and DIP respected [check]
Hint: New strategy class + injection -> Open/Closed Principle [check]
Common Mistakes:
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard