├── problem-statement.pdf ├── class diagram └── basic-ride-sharing-app.png ├── src ├── main │ └── java │ │ └── com │ │ └── company │ │ ├── RideApp.java │ │ ├── model │ │ ├── TripStatus.java │ │ ├── Rider.java │ │ ├── Driver.java │ │ └── Trip.java │ │ ├── exception │ │ ├── TripStatusException.java │ │ ├── RiderNotFoundException.java │ │ ├── TripNotFoundException.java │ │ ├── DriverNotFoundException.java │ │ ├── InvalidRideParamException.java │ │ ├── RiderAlreadyPresentException.java │ │ └── DriverAlreadyPresentException.java │ │ ├── strategy │ │ ├── PricingStrategy.java │ │ ├── DriverMatchingStrategy.java │ │ ├── DefaultPricingStrategy.java │ │ └── OptimalDriverStrategy.java │ │ └── manager │ │ ├── RiderManager.java │ │ ├── DriverManager.java │ │ └── TripManager.java └── test │ └── java │ └── com │ └── company │ ├── RiderManagerTest.java │ ├── DriverManagerTest.java │ └── TripManagerTest.java ├── README.md ├── .github └── workflows │ └── maven.yml ├── LICENSE ├── .gitignore └── pom.xml /problem-statement.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amarlearning/ride-sharing-low-level-design/HEAD/problem-statement.pdf -------------------------------------------------------------------------------- /class diagram/basic-ride-sharing-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amarlearning/ride-sharing-low-level-design/HEAD/class diagram/basic-ride-sharing-app.png -------------------------------------------------------------------------------- /src/main/java/com/company/RideApp.java: -------------------------------------------------------------------------------- 1 | package com.company; 2 | 3 | public class RideApp { 4 | public static void main(String[] args) { 5 | // ... 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/company/model/TripStatus.java: -------------------------------------------------------------------------------- 1 | package com.company.model; 2 | 3 | public enum TripStatus { 4 | IN_PROGRESS, 5 | WITHDRAWN, 6 | COMPLETED; 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/company/exception/TripStatusException.java: -------------------------------------------------------------------------------- 1 | package com.company.exception; 2 | 3 | public class TripStatusException extends RuntimeException { 4 | public TripStatusException(final String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/company/exception/RiderNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.company.exception; 2 | 3 | public class RiderNotFoundException extends RuntimeException { 4 | public RiderNotFoundException(final String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/company/exception/TripNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.company.exception; 2 | 3 | public class TripNotFoundException extends RuntimeException { 4 | public TripNotFoundException(final String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/company/exception/DriverNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.company.exception; 2 | 3 | public class DriverNotFoundException extends RuntimeException { 4 | public DriverNotFoundException(final String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/company/exception/InvalidRideParamException.java: -------------------------------------------------------------------------------- 1 | package com.company.exception; 2 | 3 | public class InvalidRideParamException extends RuntimeException { 4 | public InvalidRideParamException(final String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/company/model/Rider.java: -------------------------------------------------------------------------------- 1 | package com.company.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @Getter 7 | @AllArgsConstructor 8 | public class Rider { 9 | 10 | private int id; 11 | private String name; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/company/exception/RiderAlreadyPresentException.java: -------------------------------------------------------------------------------- 1 | package com.company.exception; 2 | 3 | public class RiderAlreadyPresentException extends RuntimeException { 4 | public RiderAlreadyPresentException(final String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/company/exception/DriverAlreadyPresentException.java: -------------------------------------------------------------------------------- 1 | package com.company.exception; 2 | 3 | public class DriverAlreadyPresentException extends RuntimeException { 4 | public DriverAlreadyPresentException(final String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Basic Ride Sharing - Low Level System Design 2 | 3 | ###### Class Diagram for low level system design of a Basic Ride Sharing App. 4 | 5 | ![Basic Ride Sharing image](./class%20diagram/basic-ride-sharing-app.png) 6 | 7 | ### Problem Statement 8 | 9 | [Link](./problem-statement.pdf) 10 | -------------------------------------------------------------------------------- /src/main/java/com/company/strategy/PricingStrategy.java: -------------------------------------------------------------------------------- 1 | package com.company.strategy; 2 | 3 | public interface PricingStrategy { 4 | 5 | Integer AMT_PER_KM = 20; 6 | 7 | double calculateFare(int origin, int destination, int seats); 8 | 9 | double calculateFareForPreferred(int origin, int destination, int seats); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/company/strategy/DriverMatchingStrategy.java: -------------------------------------------------------------------------------- 1 | package com.company.strategy; 2 | 3 | import com.company.model.Driver; 4 | import com.company.model.Rider; 5 | 6 | import java.util.List; 7 | import java.util.Optional; 8 | 9 | public interface DriverMatchingStrategy { 10 | 11 | Optional findDriver(Rider rider, List nearByDrivers, int origin, int destination); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/company/strategy/DefaultPricingStrategy.java: -------------------------------------------------------------------------------- 1 | package com.company.strategy; 2 | 3 | public class DefaultPricingStrategy implements PricingStrategy { 4 | 5 | @Override 6 | public double calculateFare(final int origin, final int destination, final int seats) { 7 | return AMT_PER_KM * (destination - origin) * seats * (seats > 1 ? 0.75 : 1); 8 | } 9 | 10 | @Override 11 | public double calculateFareForPreferred( 12 | final int origin, final int destination, final int seats) { 13 | return AMT_PER_KM * (destination - origin) * seats * (seats > 1 ? 0.5 : 0.75); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/company/strategy/OptimalDriverStrategy.java: -------------------------------------------------------------------------------- 1 | package com.company.strategy; 2 | 3 | import com.company.model.Driver; 4 | import com.company.model.Rider; 5 | import lombok.NonNull; 6 | 7 | import java.util.List; 8 | import java.util.Optional; 9 | 10 | public class OptimalDriverStrategy implements DriverMatchingStrategy { 11 | 12 | @Override 13 | public Optional findDriver( 14 | @NonNull final Rider rider, 15 | @NonNull final List nearByDrivers, 16 | final int origin, 17 | final int destination) { 18 | return nearByDrivers.stream().findAny(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up JDK 9 20 | uses: actions/setup-java@v1 21 | with: 22 | java-version: 9 23 | - name: Build with Maven 24 | run: mvn test verify package --file pom.xml 25 | -------------------------------------------------------------------------------- /src/main/java/com/company/model/Driver.java: -------------------------------------------------------------------------------- 1 | package com.company.model; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | public class Driver { 7 | 8 | private final String name; 9 | 10 | @Getter private int id; 11 | 12 | @Getter @Setter private Trip currentTrip; 13 | 14 | @Setter private boolean isAcceptingRider; 15 | 16 | public Driver(int id, String name) { 17 | this.id = id; 18 | this.name = name; 19 | this.isAcceptingRider = true; 20 | } 21 | 22 | /** 23 | * Helper method to check whether driver can take new riders or not. 24 | * 25 | * @return 26 | */ 27 | public boolean isAvailable() { 28 | return this.isAcceptingRider && this.currentTrip == null; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Amar Prakash Pandey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/com/company/model/Trip.java: -------------------------------------------------------------------------------- 1 | package com.company.model; 2 | 3 | import lombok.Getter; 4 | 5 | import java.util.UUID; 6 | 7 | public class Trip { 8 | 9 | @Getter private String id; 10 | 11 | @Getter private Rider rider; 12 | 13 | @Getter private Driver driver; 14 | 15 | private int origin; 16 | private int destination; 17 | private int seats; 18 | 19 | @Getter private double fare; 20 | 21 | @Getter private TripStatus status; 22 | 23 | public Trip( 24 | final Rider rider, 25 | final Driver driver, 26 | final int origin, 27 | final int destination, 28 | final int seats, 29 | final double fare) { 30 | this.id = UUID.randomUUID().toString(); 31 | this.rider = rider; 32 | this.driver = driver; 33 | this.origin = origin; 34 | this.destination = destination; 35 | this.seats = seats; 36 | this.fare = fare; 37 | 38 | this.status = TripStatus.IN_PROGRESS; 39 | } 40 | 41 | public void updateTrip( 42 | final int origin, final int destination, final int seats, final double fare) { 43 | this.origin = origin; 44 | this.destination = destination; 45 | this.seats = seats; 46 | this.fare = fare; 47 | } 48 | 49 | public void endTrip() { 50 | this.status = TripStatus.COMPLETED; 51 | } 52 | 53 | public void withdrawTrip() { 54 | this.status = TripStatus.WITHDRAWN; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/company/manager/RiderManager.java: -------------------------------------------------------------------------------- 1 | package com.company.manager; 2 | 3 | import com.company.exception.RiderAlreadyPresentException; 4 | import com.company.exception.RiderNotFoundException; 5 | import com.company.model.Rider; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | /** Rider class is used to manage all riders present in the ride application. */ 11 | public class RiderManager { 12 | 13 | private Map riders = new HashMap<>(); 14 | 15 | /** 16 | * Method to add a new rider in the application. 17 | * 18 | * @param rider object 19 | * @throws RiderAlreadyPresentException exception 20 | */ 21 | public void createRider(final Rider rider) { 22 | if (riders.containsKey(rider.getId())) { 23 | throw new RiderAlreadyPresentException( 24 | "Rider with rider Id = " + rider.getId() + " already present, try with different Id."); 25 | } 26 | 27 | riders.put(rider.getId(), rider); 28 | } 29 | 30 | /** 31 | * Method to get the Rider object for the given Rider ID. 32 | * 33 | * @param riderId integer 34 | * @return Rider Object 35 | * @throws RiderNotFoundException exception 36 | */ 37 | public Rider getRider(final int riderId) { 38 | if (!riders.containsKey(riderId)) { 39 | throw new RiderNotFoundException("Rider with rider Id = " + riderId + " not found."); 40 | } 41 | 42 | return riders.get(riderId); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/com/company/RiderManagerTest.java: -------------------------------------------------------------------------------- 1 | package com.company; 2 | 3 | import com.company.exception.RiderAlreadyPresentException; 4 | import com.company.exception.RiderNotFoundException; 5 | import com.company.manager.RiderManager; 6 | import com.company.model.Rider; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | import static org.junit.jupiter.api.Assertions.assertThrows; 12 | 13 | class RiderManagerTest { 14 | 15 | RiderManager riderManager; 16 | 17 | @BeforeEach 18 | void setup() { 19 | riderManager = new RiderManager(); 20 | } 21 | 22 | @Test 23 | void test_createRiderAndGetRider() { 24 | // Given. 25 | riderManager.createRider(new Rider(1, "Amar")); 26 | riderManager.createRider(new Rider(2, "Shubham")); 27 | 28 | // When. 29 | Rider rider1 = riderManager.getRider(1); 30 | Rider rider2 = riderManager.getRider(2); 31 | 32 | // Then. 33 | assertEquals("Amar", rider1.getName()); 34 | assertEquals("Shubham", rider2.getName()); 35 | 36 | // Then. 37 | assertThrows(RiderNotFoundException.class, () -> { 38 | // When. 39 | riderManager.getRider(4); 40 | }); 41 | } 42 | 43 | @Test 44 | void test_createRiderWithDuplicateIdMethod() { 45 | // Given. 46 | Rider rider2 = new Rider(2, "Prateek"); 47 | riderManager.createRider(new Rider(1, "Amar")); 48 | riderManager.createRider(new Rider(2, "Shubham")); 49 | 50 | // Then. 51 | assertThrows(RiderAlreadyPresentException.class, () -> { 52 | // When. 53 | riderManager.createRider(rider2); 54 | }); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/com/company/DriverManagerTest.java: -------------------------------------------------------------------------------- 1 | package com.company; 2 | 3 | import com.company.exception.DriverAlreadyPresentException; 4 | import com.company.exception.DriverNotFoundException; 5 | import com.company.manager.DriverManager; 6 | import com.company.model.Driver; 7 | import org.junit.jupiter.api.BeforeEach; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | import static org.junit.jupiter.api.Assertions.assertThrows; 12 | 13 | class DriverManagerTest { 14 | 15 | DriverManager driverManager; 16 | 17 | @BeforeEach 18 | void setup() { 19 | driverManager = new DriverManager(); 20 | } 21 | 22 | @Test 23 | void test_createDriverAndGetDrivers() { 24 | // Given. 25 | Driver dummyDriver = new Driver(2, "Shubham"); 26 | driverManager.createDriver(new Driver(1, "Amar")); 27 | driverManager.createDriver(new Driver(2, "Prateek")); 28 | driverManager.createDriver(new Driver(3, "Rajat")); 29 | 30 | // Then. 31 | assertThrows(DriverAlreadyPresentException.class, () -> { 32 | // When. 33 | driverManager.createDriver(dummyDriver); 34 | }); 35 | 36 | // Then. 37 | assertEquals(3, driverManager.getDrivers().size()); 38 | } 39 | 40 | @Test 41 | void test_updateDriverAvailability() { 42 | 43 | // Given. 44 | driverManager.createDriver(new Driver(1, "Amar")); 45 | driverManager.createDriver(new Driver(2, "Prateek")); 46 | driverManager.createDriver(new Driver(3, "Rajat")); 47 | 48 | assertEquals(3, driverManager.getDrivers().size()); 49 | 50 | // When. 51 | driverManager.updateDriverAvailability(3, false); 52 | 53 | // Then. 54 | assertEquals(2, driverManager.getDrivers().size()); 55 | 56 | // Then. 57 | assertThrows(DriverNotFoundException.class, () -> { 58 | // When. 59 | driverManager.updateDriverAvailability(10, false); 60 | }); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/company/manager/DriverManager.java: -------------------------------------------------------------------------------- 1 | package com.company.manager; 2 | 3 | import com.company.exception.DriverAlreadyPresentException; 4 | import com.company.exception.DriverNotFoundException; 5 | import com.company.model.Driver; 6 | 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.stream.Collectors; 11 | 12 | /** Driver manager class is used to manage all operations related to driver. */ 13 | public class DriverManager { 14 | 15 | private Map drivers = new HashMap<>(); 16 | 17 | /** 18 | * Method to register a new Driver into the application. 19 | * 20 | * @param driver Object 21 | * @throws DriverAlreadyPresentException exception if driver is already present 22 | */ 23 | public void createDriver(final Driver driver) { 24 | if (drivers.containsKey(driver.getId())) { 25 | throw new DriverAlreadyPresentException( 26 | "Driver with driver id = " + driver.getId() + " already present, try with different Id."); 27 | } 28 | 29 | drivers.put(driver.getId(), driver); 30 | } 31 | 32 | /** 33 | * Method to update whether a driver is accepting ride or not. 34 | * 35 | * @param driverId integer 36 | * @param newAvailability boolean 37 | * @throws DriverNotFoundException If Driver not found for the given driver id. 38 | */ 39 | public void updateDriverAvailability(final int driverId, boolean newAvailability) { 40 | if (!drivers.containsKey(driverId)) { 41 | throw new DriverNotFoundException( 42 | "No driver with driver id = " + driverId + ", try with correct driver Id."); 43 | } 44 | 45 | drivers.get(driverId).setAcceptingRider(newAvailability); 46 | } 47 | 48 | public List getDrivers() { 49 | return drivers.values().stream().filter(Driver::isAvailable).collect(Collectors.toList()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | *# 26 | *.iml 27 | *.ipr 28 | *.iws 29 | *.sw? 30 | *~ 31 | .#* 32 | .*.md.html 33 | .DS_Store 34 | .classpath 35 | .gradle 36 | .idea 37 | .metadata 38 | .project 39 | .recommenders 40 | .settings 41 | /build 42 | /code 43 | MANIFEST.MF 44 | _site/ 45 | activemq-data 46 | bin 47 | build 48 | build.log 49 | dependency-reduced-pom.xml 50 | dump.rdb 51 | interpolated*.xml 52 | lib/ 53 | manifest.yml 54 | overridedb.* 55 | settings.xml 56 | target 57 | transaction-logs 58 | .flattened-pom.xml 59 | secrets.yml 60 | .gradletasknamecache 61 | .sts4-cache 62 | 63 | bin/ 64 | tmp/ 65 | *.tmp 66 | *.bak 67 | *.swp 68 | *~.nib 69 | local.properties 70 | .settings/ 71 | .loadpath 72 | 73 | # External tool builders 74 | .externalToolBuilders/ 75 | 76 | # Locally stored "Eclipse launch configurations" 77 | *.launch 78 | 79 | # PyDev specific (Python IDE for Eclipse) 80 | *.pydevproject 81 | 82 | # CDT-specific (C/C++ Development Tooling) 83 | .cproject 84 | 85 | # CDT- autotools 86 | .autotools 87 | 88 | # Java annotation processor (APT) 89 | .factorypath 90 | 91 | # PDT-specific (PHP Development Tools) 92 | .buildpath 93 | 94 | # sbteclipse plugin 95 | .target 96 | 97 | # Tern plugin 98 | .tern-project 99 | 100 | # TeXlipse plugin 101 | .texlipse 102 | 103 | # STS (Spring Tool Suite) 104 | .springBeans 105 | 106 | # Code Recommenders 107 | .recommenders/ 108 | 109 | # Annotation Processing 110 | .apt_generated/ 111 | 112 | # Scala IDE specific (Scala & Java development for Eclipse) 113 | .cache-main 114 | .scala_dependencies 115 | .worksheet 116 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.example 8 | ride-sharing-low-level-design 9 | 1.0-SNAPSHOT 10 | jar 11 | 12 | 13 | UTF-8 14 | 9 15 | 9 16 | 17 | 18 | 19 | 20 | org.junit.jupiter 21 | junit-jupiter-engine 22 | 5.5.2 23 | test 24 | 25 | 26 | org.projectlombok 27 | lombok 28 | 1.18.16 29 | provided 30 | 31 | 32 | 33 | 34 | 35 | 36 | org.apache.maven.plugins 37 | maven-surefire-plugin 38 | 2.22.0 39 | 40 | 41 | 42 | org.jacoco 43 | jacoco-maven-plugin 44 | 0.8.6 45 | 46 | 47 | prepare-agent 48 | 49 | prepare-agent 50 | 51 | 52 | 53 | report 54 | 55 | report 56 | 57 | 58 | 59 | 60 | jacoco-check 61 | 62 | check 63 | 64 | 65 | 66 | 67 | PACKAGE 68 | 69 | 70 | CLASS 71 | MISSEDCOUNT 72 | 1 73 | 74 | 75 | 76 | 77 | CLASS 78 | 79 | *Test 80 | *RideApp 81 | 82 | 83 | 84 | LINE 85 | COVEREDRATIO 86 | 90% 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /src/test/java/com/company/TripManagerTest.java: -------------------------------------------------------------------------------- 1 | package com.company; 2 | 3 | import com.company.exception.DriverNotFoundException; 4 | import com.company.exception.InvalidRideParamException; 5 | import com.company.exception.TripNotFoundException; 6 | import com.company.exception.TripStatusException; 7 | import com.company.manager.DriverManager; 8 | import com.company.manager.RiderManager; 9 | import com.company.manager.TripManager; 10 | import com.company.model.Driver; 11 | import com.company.model.Rider; 12 | import com.company.model.Trip; 13 | import com.company.model.TripStatus; 14 | import com.company.strategy.DefaultPricingStrategy; 15 | import com.company.strategy.OptimalDriverStrategy; 16 | import org.junit.jupiter.api.Assertions; 17 | import org.junit.jupiter.api.BeforeEach; 18 | import org.junit.jupiter.api.Test; 19 | 20 | import java.util.stream.IntStream; 21 | 22 | import static org.junit.jupiter.api.Assertions.*; 23 | 24 | class TripManagerTest { 25 | 26 | TripManager tripManager; 27 | Driver driver1, driver2; 28 | Rider rider1, rider2, rider3; 29 | 30 | @BeforeEach 31 | void setup() { 32 | 33 | driver1 = new Driver(1, "Driver1"); 34 | driver2 = new Driver(2, "Driver2"); 35 | 36 | DriverManager driverManager = new DriverManager(); 37 | driverManager.createDriver(driver1); 38 | driverManager.createDriver(driver2); 39 | 40 | rider1 = new Rider(1, "Rider1"); 41 | rider2 = new Rider(2, "Rider2"); 42 | rider3 = new Rider(3, "Rider3"); 43 | 44 | RiderManager riderManager = new RiderManager(); 45 | riderManager.createRider(rider1); 46 | riderManager.createRider(rider2); 47 | riderManager.createRider(rider3); 48 | 49 | tripManager = 50 | new TripManager( 51 | riderManager, driverManager, new DefaultPricingStrategy(), new OptimalDriverStrategy()); 52 | } 53 | 54 | @Test 55 | void test_createRideMethod() { 56 | 57 | // Given. 58 | // Driver1 booked by rider1, one driver left now. 59 | tripManager.createTrip(rider1, 50, 60, 1); 60 | 61 | // Driver2 booked by rider2, zero driver left now. 62 | tripManager.createTrip(rider2, 60, 70, 2); 63 | 64 | // Then. 65 | assertThrows( 66 | DriverNotFoundException.class, 67 | () -> { 68 | // When. 69 | tripManager.createTrip(rider3, 80, 100, 2); 70 | }); 71 | 72 | assertThrows( 73 | InvalidRideParamException.class, 74 | () -> { 75 | tripManager.createTrip(rider3, 50, 40, 1); 76 | }); 77 | } 78 | 79 | @Test 80 | void test_updateTripWithWrongTripId() { 81 | 82 | // Then. 83 | Assertions.assertThrows( 84 | InvalidRideParamException.class, 85 | () -> { 86 | // When. 87 | tripManager.updateTrip("random-id", 40, 10, 1); 88 | }); 89 | } 90 | 91 | @Test 92 | void test_updateTripWithWrongDetails() { 93 | 94 | // Given. 95 | String tripId = tripManager.createTrip(rider1, 10, 20, 2); 96 | 97 | // Then. 98 | Assertions.assertThrows( 99 | InvalidRideParamException.class, 100 | () -> { 101 | // When. 102 | tripManager.updateTrip(tripId, 40, 10, 1); 103 | }); 104 | } 105 | 106 | @Test 107 | void test_updateTripWhichIsAlreadyCompletedOrWithdrawn() { 108 | 109 | // Given. 110 | String tripId = tripManager.createTrip(rider1, 10, 20, 2); 111 | Driver driver = tripManager.getDriverForTrip(tripId).get(); 112 | tripManager.endTrip(driver); 113 | 114 | // Then. 115 | Assertions.assertThrows( 116 | TripStatusException.class, 117 | () -> { 118 | // When. 119 | tripManager.updateTrip(tripId, 10, 50, 1); 120 | }); 121 | } 122 | 123 | @Test 124 | void test_updateTripFareBasedOnNewDetails() { 125 | 126 | // Given. 127 | String tripId = tripManager.createTrip(rider1, 10, 20, 2); 128 | 129 | // When. 130 | tripManager.updateTrip(tripId, 20, 40, 1); 131 | Driver driver = tripManager.getDriverForTrip(tripId).get(); 132 | 133 | // Then. 134 | Assertions.assertEquals(400, tripManager.endTrip(driver)); 135 | } 136 | 137 | @Test 138 | void test_updateTripWithInvalidTripId() { 139 | 140 | // Then. 141 | Assertions.assertThrows( 142 | TripNotFoundException.class, 143 | () -> { 144 | // When. 145 | tripManager.updateTrip("1234", 10, 20, 2); 146 | }); 147 | } 148 | 149 | @Test 150 | void test_withdrawTrip() { 151 | 152 | // Given. 153 | String tripId = tripManager.createTrip(rider1, 10, 20, 2); 154 | 155 | // When. 156 | tripManager.withdrawTrip(tripId); 157 | Trip trip = 158 | tripManager.tripHistory(rider1).parallelStream() 159 | .filter(t -> t.getId().equals(tripId)) 160 | .findAny() 161 | .get(); 162 | 163 | // Then. 164 | Assertions.assertEquals(TripStatus.WITHDRAWN, trip.getStatus()); 165 | } 166 | 167 | @Test 168 | void test_withdrawTripWithInvalidTripId() { 169 | 170 | // Given. 171 | String tripId = tripManager.createTrip(rider1, 10, 20, 2); 172 | 173 | // Then. 174 | Assertions.assertThrows( 175 | TripNotFoundException.class, 176 | () -> { 177 | // When. 178 | tripManager.withdrawTrip("randomId"); 179 | }); 180 | } 181 | 182 | @Test 183 | void test_withdrawTripWhichIsAlreadyCompleted() { 184 | 185 | // Given. 186 | String tripId = tripManager.createTrip(rider1, 10, 20, 2); 187 | Driver driver = tripManager.getDriverForTrip(tripId).get(); 188 | tripManager.endTrip(driver); 189 | 190 | // Then. 191 | Assertions.assertThrows( 192 | TripStatusException.class, 193 | () -> { 194 | // When. 195 | tripManager.withdrawTrip(tripId); 196 | }); 197 | } 198 | 199 | @Test 200 | void test_endRideAndAddAcceptNewRiderRequest() { 201 | 202 | // Given. 203 | String trip1 = tripManager.createTrip(rider1, 20, 60, 2); 204 | String trip2 = tripManager.createTrip(rider2, 40, 70, 2); 205 | 206 | // End the trip of rider2 and book the ride of rider3. 207 | Driver driverForRider2 = tripManager.getDriverForTrip(trip2).get(); 208 | 209 | // When. 210 | assertEquals(900, tripManager.endTrip(driverForRider2)); 211 | 212 | // Then. 213 | tripManager.createTrip(rider3, 80, 100, 2); 214 | } 215 | 216 | @Test 217 | void test_getFareForDriverWhenNotRiding() { 218 | // Then. 219 | assertThrows( 220 | TripNotFoundException.class, 221 | () -> { 222 | // When. 223 | tripManager.endTrip(driver1); 224 | }); 225 | } 226 | 227 | @Test 228 | void test_preferredRiderFareCalculation() { 229 | 230 | // When rider has completed more than 10 rides, he become preferred rider. 231 | IntStream.range(1, 12) 232 | .forEach( 233 | i -> { 234 | String tripId = tripManager.createTrip(rider1, i * 10, (i + 1) * 10, 1); 235 | Driver driverForRider1 = tripManager.getDriverForTrip(tripId).get(); 236 | tripManager.endTrip(driverForRider1); 237 | }); 238 | 239 | // Then. 240 | assertTrue(tripManager.tripHistory(rider1).size() == 11); 241 | 242 | String tripId = tripManager.createTrip(rider1, 10, 20, 2); 243 | Driver driverForRider1 = tripManager.getDriverForTrip(tripId).get(); 244 | 245 | assertEquals(200, tripManager.endTrip(driverForRider1)); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/main/java/com/company/manager/TripManager.java: -------------------------------------------------------------------------------- 1 | package com.company.manager; 2 | 3 | import com.company.exception.DriverNotFoundException; 4 | import com.company.exception.InvalidRideParamException; 5 | import com.company.exception.TripNotFoundException; 6 | import com.company.exception.TripStatusException; 7 | import com.company.model.Driver; 8 | import com.company.model.Rider; 9 | import com.company.model.Trip; 10 | import com.company.model.TripStatus; 11 | import com.company.strategy.DriverMatchingStrategy; 12 | import com.company.strategy.PricingStrategy; 13 | 14 | import java.util.*; 15 | 16 | /** TripManager class is used to manage all riders and drivers. */ 17 | public class TripManager { 18 | 19 | private RiderManager riderManager; 20 | private DriverManager driverManager; 21 | private PricingStrategy pricingStrategy; 22 | private DriverMatchingStrategy driverMatchingStrategy; 23 | 24 | /** Mapping of Rider with it's associated trips. */ 25 | private Map> trips = new HashMap<>(); 26 | 27 | public TripManager( 28 | final RiderManager riderManager, 29 | final DriverManager driverManager, 30 | final PricingStrategy pricingStrategy, 31 | final DriverMatchingStrategy driverMatchingStrategy) { 32 | this.riderManager = riderManager; 33 | this.driverManager = driverManager; 34 | this.pricingStrategy = pricingStrategy; 35 | this.driverMatchingStrategy = driverMatchingStrategy; 36 | } 37 | 38 | /** 39 | * Method to create a trip for rider. 40 | * 41 | * @param rider Object. 42 | * @param origin Integer. 43 | * @param destination Integer. 44 | * @param seats Integer. 45 | * @return Trip Id. 46 | */ 47 | public String createTrip( 48 | final Rider rider, final int origin, final int destination, final int seats) { 49 | 50 | // Throw exception if origin is greater than destination 51 | if (origin >= destination) { 52 | throw new InvalidRideParamException( 53 | "Origin should always be greater than exception, try with valid request."); 54 | } 55 | 56 | // Find a driver for this ride if not found throw exception. 57 | List drivers = driverManager.getDrivers(); 58 | Optional matchedDriver = 59 | driverMatchingStrategy.findDriver(rider, drivers, origin, destination); 60 | 61 | if (!matchedDriver.isPresent()) { 62 | throw new DriverNotFoundException("Driver not found, Please try after some time"); 63 | } 64 | 65 | // Create a trip for rider if all's good. 66 | Driver driver = matchedDriver.get(); 67 | 68 | double fare = calculateFare(rider, origin, destination, seats); 69 | 70 | Trip trip = new Trip(rider, driver, origin, destination, seats, fare); 71 | 72 | if (!trips.containsKey(rider.getId())) { 73 | trips.put(rider.getId(), new ArrayList<>()); 74 | } 75 | 76 | trips.get(rider.getId()).add(trip); 77 | driver.setCurrentTrip(trip); 78 | 79 | return trip.getId(); 80 | } 81 | 82 | /** 83 | * Update the trip details using Trip Id. 84 | * 85 | * @param tripId String. 86 | * @param origin Integer. 87 | * @param destination Integer. 88 | * @param seats Integer. 89 | */ 90 | public void updateTrip( 91 | final String tripId, final int origin, final int destination, final int seats) { 92 | 93 | // Throw exception if origin is greater than destination 94 | if (origin >= destination) { 95 | throw new InvalidRideParamException( 96 | "Origin should always be greater than exception, try with valid request."); 97 | } 98 | 99 | Optional optionalTrip = this.getTrip(tripId); 100 | 101 | if (!optionalTrip.isPresent()) { 102 | throw new TripNotFoundException( 103 | "No Trip found for the given Id = " + tripId + ", please try with valid Trip Id."); 104 | } 105 | 106 | Trip trip = optionalTrip.get(); 107 | 108 | if (trip.getStatus().equals(TripStatus.COMPLETED) 109 | || trip.getStatus().equals(TripStatus.WITHDRAWN)) { 110 | throw new TripStatusException( 111 | "Trip has already been completed or withdrawn try with valid Trip Id."); 112 | } 113 | 114 | double fare = calculateFare(trip.getRider(), origin, destination, seats); 115 | 116 | trip.updateTrip(origin, destination, seats, fare); 117 | } 118 | 119 | /** 120 | * Method to withdraw trip using trip Id. 121 | * 122 | * @param tripId String. 123 | */ 124 | public void withdrawTrip(final String tripId) { 125 | 126 | Optional optionalTrip = this.getTrip(tripId); 127 | 128 | if (!optionalTrip.isPresent()) { 129 | throw new TripNotFoundException( 130 | "No Trip found for the given Id = " + tripId + ", please try with valid Trip Id."); 131 | } 132 | 133 | Trip trip = optionalTrip.get(); 134 | 135 | if (trip.getStatus().equals(TripStatus.COMPLETED)) { 136 | throw new TripStatusException("Trip has already been completed, can't withdraw now."); 137 | } 138 | 139 | Driver driver = trip.getDriver(); 140 | driver.setCurrentTrip(null); 141 | trip.withdrawTrip(); 142 | } 143 | 144 | /** 145 | * Method to end the trip. 146 | * 147 | * @param driver Object. 148 | * @return Calculated Fare. 149 | */ 150 | public double endTrip(final Driver driver) { 151 | if (driver.getCurrentTrip() == null) { 152 | throw new TripNotFoundException("Currently rider is not riding, please try again."); 153 | } 154 | 155 | double fare = driver.getCurrentTrip().getFare(); 156 | driver.getCurrentTrip().endTrip(); 157 | driver.setCurrentTrip(null); 158 | 159 | return fare; 160 | } 161 | 162 | /** 163 | * Method to get all trips done by a particular rider. 164 | * 165 | * @param rider Object. 166 | * @return List of Trip. 167 | */ 168 | public List tripHistory(final Rider rider) { 169 | return trips.getOrDefault(rider.getId(), new ArrayList<>()); 170 | } 171 | 172 | /** 173 | * Helper method to get the respective driver for the given rider. 174 | * 175 | * @param tripId Integer. 176 | * @return Driver. 177 | */ 178 | public Optional getDriverForTrip(final String tripId) { 179 | Optional trip = 180 | this.trips.values().stream() 181 | .flatMap(list -> list.stream()) 182 | .filter(t -> t.getId().equals(tripId)) 183 | .findFirst(); 184 | 185 | return Optional.of(trip.get().getDriver()); 186 | } 187 | 188 | /** 189 | * Helper Method to check if the given rider is preferred or not. 190 | * 191 | * @param rider Object. 192 | * @return Boolean. 193 | */ 194 | private boolean isRiderPreferred(final Rider rider) { 195 | return tripHistory(rider).size() >= 10; 196 | } 197 | 198 | /** 199 | * Helper method to get trip for the given Trip Id. 200 | * 201 | * @param tripId Integer. 202 | * @return Trip. 203 | */ 204 | private Optional getTrip(final String tripId) { 205 | return trips.values().stream() 206 | .flatMap(list -> list.stream()) 207 | .filter(t -> t.getId().equals(tripId)) 208 | .findFirst(); 209 | } 210 | 211 | /** 212 | * Helper method to calculate fare. 213 | * 214 | * @param rider Object. 215 | * @param origin Integer. 216 | * @param destination Integer. 217 | * @param seats Integer. 218 | * @return double. 219 | */ 220 | private double calculateFare(final Rider rider, int origin, int destination, int seats) { 221 | return isRiderPreferred(rider) 222 | ? pricingStrategy.calculateFareForPreferred(origin, destination, seats) 223 | : pricingStrategy.calculateFare(origin, destination, seats); 224 | } 225 | } 226 | --------------------------------------------------------------------------------