📂
Interview Prep
LLD Questions
LLD Questions
  • LLD Introduction
  • Meeting Scheduler
  • Distributed Cache System
  • Rate limiter
  • Multi-Tier Elevator System
  • MyGate System
  • Uber Sytem LLD
  • Parking Lot System
  • Online book store
  • Library Management System
  • Movie Ticket Booking System
  • Hotel Management System
  • File Storage System
    • Solution 2
  • Chat Application
  • Social Media Platform
  • Notification System
  • Airline Reservation System
  • ATM System
  • E-commerce Website
  • Food Delivery System
  • Shopping Cart System
  • URL Shortener system
  • Chess Tournament System
  • Threading therory
  • OOP questions
Powered by GitBook
On this page

Uber Sytem LLD

Low-Level Design (LLD) for an Application like Uber (Ride-Hailing Service)

In this Low-Level Design (LLD) for a Ride-Hailing Service like Uber, we will focus on core functionality such as rider management, driver management, ride matching, ride tracking, pricing, and notifications. We'll also employ design patterns to ensure that the system is maintainable and scalable.

We will discuss the following key questions for designing the system:

  • Entities and Relationships: What are the entities involved in this application? How do they relate to each other?

  • User Management: How will we manage riders and drivers?

  • Ride Matching: How will the system match riders with available drivers?

  • Pricing: How will the fare be calculated based on distance, time, and type of ride?

  • Notification System: How will the system notify users (riders and drivers) about ride requests, cancellations, or other important events?

  • Ride Tracking: How will the system track the status and location of ongoing rides?

Key Components

  1. User Management: For managing riders and drivers.

  2. Ride Management: To handle ride requests, ride status, and ride completion.

  3. Ride Matching: Algorithm to match riders with nearby available drivers.

  4. Pricing Engine: For calculating the fare for a ride.

  5. Notification System: To send updates to users about ride status.

  6. Location Tracking: To track the location of drivers and riders in real-time.

Design Patterns Used

  • Factory Pattern: For creating user objects (Riders, Drivers).

  • Strategy Pattern: For different pricing models.

  • Observer Pattern: For notifying riders and drivers about ride updates.

  • Command Pattern: For processing actions such as ride requests, cancellations, and updates.

  • Singleton Pattern: For managing shared resources like the RideMatchingEngine and NotificationService.


Classes and Data Structures

1. User Class (Abstract Class)

We will create an abstract User class, which will be extended by Rider and Driver.

abstract class User {
    protected String name;
    protected String phoneNumber;
    protected String email;

    public User(String name, String phoneNumber, String email) {
        this.name = name;
        this.phoneNumber = phoneNumber;
        this.email = email;
    }

    public abstract void performAction();
}

class Rider extends User {
    private String location;

    public Rider(String name, String phoneNumber, String email, String location) {
        super(name, phoneNumber, email);
        this.location = location;
    }

    public void performAction() {
        System.out.println(name + " is looking for a ride.");
    }

    public String getLocation() {
        return location;
    }
}

class Driver extends User {
    private String location;
    private boolean isAvailable;

    public Driver(String name, String phoneNumber, String email, String location) {
        super(name, phoneNumber, email);
        this.location = location;
        this.isAvailable = true; // Driver is initially available
    }

    public void performAction() {
        System.out.println(name + " is available to take rides.");
    }

    public String getLocation() {
        return location;
    }

    public boolean isAvailable() {
        return isAvailable;
    }

    public void setAvailability(boolean isAvailable) {
        this.isAvailable = isAvailable;
    }
}

2. UserFactory Class (Factory Pattern)

The UserFactory class implements the Factory Pattern to create instances of Rider and Driver.

class UserFactory {
    public static User createUser(String type, String name, String phoneNumber, String email, String location) {
        switch (type) {
            case "Rider":
                return new Rider(name, phoneNumber, email, location);
            case "Driver":
                return new Driver(name, phoneNumber, email, location);
            default:
                throw new IllegalArgumentException("Invalid user type");
        }
    }
}

3. Ride Class

The Ride class manages the details of each ride, including its status (requested, in-progress, completed, etc.).

class Ride {
    private Rider rider;
    private Driver driver;
    private String startLocation;
    private String endLocation;
    private String status; // "Requested", "In-Progress", "Completed"
    private double fare;

    public Ride(Rider rider, Driver driver, String startLocation, String endLocation) {
        this.rider = rider;
        this.driver = driver;
        this.startLocation = startLocation;
        this.endLocation = endLocation;
        this.status = "Requested";
    }

    public void startRide() {
        this.status = "In-Progress";
        System.out.println("Ride started from " + startLocation + " to " + endLocation);
    }

    public void completeRide(double fare) {
        this.status = "Completed";
        this.fare = fare;
        System.out.println("Ride completed. Fare: " + fare);
    }

    public String getStatus() {
        return status;
    }

    public void setStatus(String status) {
        this.status = status;
    }

    public double getFare() {
        return fare;
    }
}

4. RideMatchingEngine Class (Singleton Pattern)

The RideMatchingEngine is responsible for matching riders with available drivers.

class RideMatchingEngine {
    private static RideMatchingEngine instance;

    private RideMatchingEngine() {}

    public static RideMatchingEngine getInstance() {
        if (instance == null) {
            instance = new RideMatchingEngine();
        }
        return instance;
    }

    public Driver matchDriver(Rider rider, List<Driver> drivers) {
        for (Driver driver : drivers) {
            if (driver.isAvailable() && !driver.getLocation().equals(rider.getLocation())) {
                return driver;
            }
        }
        return null; // No driver found
    }
}

5. PricingEngine Class (Strategy Pattern)

The PricingEngine uses the Strategy Pattern to allow different pricing strategies based on distance, time, and surge pricing.

interface PricingStrategy {
    double calculateFare(double distance, double time);
}

class StandardPricingStrategy implements PricingStrategy {
    @Override
    public double calculateFare(double distance, double time) {
        return (distance * 1.5) + (time * 0.5);
    }
}

class SurgePricingStrategy implements PricingStrategy {
    @Override
    public double calculateFare(double distance, double time) {
        return (distance * 2.0) + (time * 0.75);
    }
}

class PricingEngine {
    private PricingStrategy pricingStrategy;

    public PricingEngine(PricingStrategy pricingStrategy) {
        this.pricingStrategy = pricingStrategy;
    }

    public double calculateFare(double distance, double time) {
        return pricingStrategy.calculateFare(distance, time);
    }
}

6. Notification System (Observer Pattern)

The NotificationSystem is responsible for notifying riders and drivers about ride updates (e.g., matching found, ride started, ride completed).

interface Observer {
    void update(String message);
}

class RiderObserver implements Observer {
    private Rider rider;

    public RiderObserver(Rider rider) {
        this.rider = rider;
    }

    @Override
    public void update(String message) {
        System.out.println("Notification to Rider: " + message);
    }
}

class DriverObserver implements Observer {
    private Driver driver;

    public DriverObserver(Driver driver) {
        this.driver = driver;
    }

    @Override
    public void update(String message) {
        System.out.println("Notification to Driver: " + message);
    }
}

class NotificationSystem {
    private List<Observer> observers = new ArrayList<>();

    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    public void notifyObservers(String message) {
        for (Observer observer : observers) {
            observer.update(message);
        }
    }
}

7. RideRequestCommand (Command Pattern)

The Command Pattern is used to process the actions of requesting, starting, and completing a ride.

interface Command {
    void execute();
}

class RideRequestCommand implements Command {
    private RideMatchingEngine rideMatchingEngine;
    private Rider rider;
    private List<Driver> drivers;
    private NotificationSystem notificationSystem;

    public RideRequestCommand(RideMatchingEngine rideMatchingEngine, Rider rider, List<Driver> drivers, NotificationSystem notificationSystem) {
        this.rideMatchingEngine = rideMatchingEngine;
        this.rider = rider;
        this.drivers = drivers;
        this.notificationSystem = notificationSystem;
    }

    @Override
    public void execute() {
        Driver driver = rideMatchingEngine.matchDriver(rider, drivers);
        if (driver != null) {
            notificationSystem.notifyObservers("Driver matched for rider " + rider.getLocation());
        } else {
            notificationSystem.notifyObservers("No drivers available for rider " + rider.getLocation());
        }
    }
}

Main Class to Demonstrate the System

public class UberApplication {
    public static void main(String[] args) {
        // Create users using the factory
        Rider rider = (Rider) UserFactory.createUser("Rider", "John", "1234567890", "john@example.com", "Location A");
        Driver driver = (Driver) UserFactory.createUser("Driver", "Alex", "0987654321", "alex@example.com", "Location B");

        // Create a notification system
        NotificationSystem notificationSystem = new NotificationSystem();
        notificationSystem.addObserver(new RiderObserver(rider));
        notificationSystem.addObserver(new DriverObserver(driver));

        // Create ride matching engine
        RideMatchingEngine rideMatchingEngine = RideMatchingEngine.getInstance();

        // Create and execute ride request command
        Command rideRequestCommand = new RideRequestCommand(rideMatchingEngine, rider, List.of(driver), notificationSystem);
        rideRequestCommand.execute();
    }
}

Explanation of Design Patterns Used

  1. Factory Pattern:

    • The UserFactory class dynamically creates Rider and Driver objects based on input.

  2. Strategy Pattern:

    • The PricingStrategy interface and its implementations (StandardPricingStrategy, SurgePricingStrategy) allow different pricing strategies to be applied.

  3. Observer Pattern:

    • The NotificationSystem uses the Observer pattern to notify Rider and Driver observers about ride updates.

  4. Command Pattern:

    • The Command interface and RideRequestCommand class encapsulate the logic for requesting rides, improving modularity and flexibility.

  5. Singleton Pattern:

    • The RideMatchingEngine is implemented as a Singleton to ensure that there is only one instance managing ride matching.


Conclusion

This Low-Level Design of the Uber-like Application handles core functionality such as user management, ride matching, pricing, notifications, and ride tracking efficiently using design patterns like Factory, Strategy, Observer, Command, and Singleton. This design ensures scalability, maintainability, and flexibility, allowing future features such as dynamic pricing or multi-ride requests to be easily integrated.

PreviousMyGate SystemNextParking Lot System

Last updated 2 months ago