// Core package structure package com.rideshare.system; import java.time.LocalDateTime; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; // --------- CORE DOMAIN MODELS --------- // Base class for all entities that need an ID abstract class Entity { protected final String id; public Entity() { this.id = UUID.randomUUID().toString(); } public Entity(String id) { this.id = id; } public String getId() { return id; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Entity entity = (Entity) o; return Objects.equals(id, entity.id); } @Override public int hashCode() { return Objects.hash(id); } } // User representation - can be a driver, passenger, or both class User extends Entity { private String name; private String email; private String phoneNumber; private String password; // In a real system, this would be hashed and secured private List<PaymentMethod> paymentMethods; private UserRole role; private double rating; private int totalRatings; public User(String name, String email, String phoneNumber, String password, UserRole role) { super(); this.name = name; this.email = email; this.phoneNumber = phoneNumber; this.password = password; this.role = role; this.paymentMethods = new ArrayList<>(); this.rating = 0.0; this.totalRatings = 0; } // Getters and Setters public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getPhoneNumber() { return phoneNumber; } public void setPhoneNumber(String phoneNumber) { this.phoneNumber = phoneNumber; } public UserRole getRole() { return role; } public void setRole(UserRole role) { this.role = role; } public void addPaymentMethod(PaymentMethod paymentMethod) { this.paymentMethods.add(paymentMethod); } public List<PaymentMethod> getPaymentMethods() { return Collections.unmodifiableList(paymentMethods); } public double getRating() { return rating; } public void addRating(double newRating) { if (newRating < 1.0 || newRating > 5.0) { throw new IllegalArgumentException("Rating must be between 1 and 5"); } double totalRatingPoints = rating * totalRatings; totalRatings++; totalRatingPoints += newRating; rating = totalRatingPoints / totalRatings; } // Validate password - in real systems, use proper password hashing public boolean validatePassword(String inputPassword) { return password.equals(inputPassword); } } // Represents a vehicle registered in the system class Vehicle extends Entity { private String model; private String make; private String licensePlate; private int year; private int capacity; private VehicleType type; private VehicleStatus status; private String ownerId; // Reference to User public Vehicle(String model, String make, String licensePlate, int year, int capacity, VehicleType type, String ownerId) { super(); this.model = model; this.make = make; this.licensePlate = licensePlate; this.year = year; this.capacity = capacity; this.type = type; this.ownerId = ownerId; this.status = VehicleStatus.AVAILABLE; } // Getters and Setters public String getModel() { return model; } public String getMake() { return make; } public String getLicensePlate() { return licensePlate; } public int getYear() { return year; } public int getCapacity() { return capacity; } public VehicleType getType() { return type; } public String getOwnerId() { return ownerId; } public VehicleStatus getStatus() { return status; } public void setStatus(VehicleStatus status) { this.status = status; } } // Location representation with latitude and longitude class Location { private double latitude; private double longitude; private String address; // Optional human-readable address public Location(double latitude, double longitude) { this.latitude = latitude; this.longitude = longitude; } public Location(double latitude, double longitude, String address) { this.latitude = latitude; this.longitude = longitude; this.address = address; } // Getters public double getLatitude() { return latitude; } public double getLongitude() { return longitude; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } // Calculate distance between two locations using Haversine formula public double distanceTo(Location other) { final int EARTH_RADIUS_KM = 6371; // Earth's radius in kilometers double lat1 = Math.toRadians(this.latitude); double lon1 = Math.toRadians(this.longitude); double lat2 = Math.toRadians(other.latitude); double lon2 = Math.toRadians(other.longitude); double dLat = lat2 - lat1; double dLon = lon2 - lon1; double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) * Math.sin(dLon / 2); double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return EARTH_RADIUS_KM * c; } } // Represents a ride request class RideRequest extends Entity { private String passengerId; private Location pickupLocation; private Location dropoffLocation; private LocalDateTime requestTime; private int passengerCount; private RideType rideType; private RideRequestStatus status; private List<String> matchedDriverIds; public RideRequest(String passengerId, Location pickupLocation, Location dropoffLocation, int passengerCount, RideType rideType) { super(); this.passengerId = passengerId; this.pickupLocation = pickupLocation; this.dropoffLocation = dropoffLocation; this.requestTime = LocalDateTime.now(); this.passengerCount = passengerCount; this.rideType = rideType; this.status = RideRequestStatus.PENDING; this.matchedDriverIds = new ArrayList<>(); } // Getters and Setters public String getPassengerId() { return passengerId; } public Location getPickupLocation() { return pickupLocation; } public Location getDropoffLocation() { return dropoffLocation; } public LocalDateTime getRequestTime() { return requestTime; } public int getPassengerCount() { return passengerCount; } public RideType getRideType() { return rideType; } public RideRequestStatus getStatus() { return status; } public void setStatus(RideRequestStatus status) { this.status = status; } public List<String> getMatchedDriverIds() { return Collections.unmodifiableList(matchedDriverIds); } public void addMatchedDriver(String driverId) { this.matchedDriverIds.add(driverId); } public boolean removeMatchedDriver(String driverId) { return this.matchedDriverIds.remove(driverId); } // Estimated duration in minutes based on distance public int getEstimatedDuration() { double distanceKm = pickupLocation.distanceTo(dropoffLocation); // Assuming average speed of 30 km/h return (int) Math.ceil(distanceKm * 60 / 30); } // Calculate estimated fare public double getEstimatedFare(PricingStrategy pricingStrategy) { return pricingStrategy.calculatePrice(this); } } // Represents an active ride class Ride extends Entity { private String rideRequestId; private String driverId; private String vehicleId; private LocalDateTime startTime; private LocalDateTime endTime; private RideStatus status; private double fare; private String paymentId; private List<Location> actualRoute; public Ride(String rideRequestId, String driverId, String vehicleId, double fare) { super(); this.rideRequestId = rideRequestId; this.driverId = driverId; this.vehicleId = vehicleId; this.fare = fare; this.status = RideStatus.ASSIGNED; this.actualRoute = new ArrayList<>(); } // Getters and Setters public String getRideRequestId() { return rideRequestId; } public String getDriverId() { return driverId; } public String getVehicleId() { return vehicleId; } public RideStatus getStatus() { return status; } public void setStatus(RideStatus status) { this.status = status; } public double getFare() { return fare; } public void setFare(double fare) { this.fare = fare; } public LocalDateTime getStartTime() { return startTime; } public LocalDateTime getEndTime() { return endTime; } public String getPaymentId() { return paymentId; } public void setPaymentId(String paymentId) { this.paymentId = paymentId; } public void addRoutePoint(Location location) { this.actualRoute.add(location); } public List<Location> getActualRoute() { return Collections.unmodifiableList(actualRoute); } public void startRide() { if (this.status != RideStatus.ASSIGNED) { throw new IllegalStateException("Ride must be assigned before starting"); } this.startTime = LocalDateTime.now(); this.status = RideStatus.IN_PROGRESS; } public void completeRide() { if (this.status != RideStatus.IN_PROGRESS) { throw new IllegalStateException("Ride must be in progress before completing"); } this.endTime = LocalDateTime.now(); this.status = RideStatus.COMPLETED; } public void cancelRide(String reason) { if (this.status == RideStatus.COMPLETED) { throw new IllegalStateException("Cannot cancel a completed ride"); } this.status = RideStatus.CANCELLED; } public int getDurationMinutes() { if (startTime == null || endTime == null) { return 0; } return (int) java.time.Duration.between(startTime, endTime).toMinutes(); } } // Payment representation class Payment extends Entity { private String rideId; private String userId; private PaymentMethod paymentMethod; private double amount; private LocalDateTime paymentTime; private PaymentStatus status; private String transactionId; public Payment(String rideId, String userId, PaymentMethod paymentMethod, double amount) { super(); this.rideId = rideId; this.userId = userId; this.paymentMethod = paymentMethod; this.amount = amount; this.paymentTime = LocalDateTime.now(); this.status = PaymentStatus.PENDING; } // Getters and Setters public String getRideId() { return rideId; } public String getUserId() { return userId; } public PaymentMethod getPaymentMethod() { return paymentMethod; } public double getAmount() { return amount; } public LocalDateTime getPaymentTime() { return paymentTime; } public PaymentStatus getStatus() { return status; } public void setStatus(PaymentStatus status) { this.status = status; } public String getTransactionId() { return transactionId; } public void setTransactionId(String transactionId) { this.transactionId = transactionId; } public void processPayment() { // In a real system, this would integrate with a payment processor // For now, we'll simulate a successful payment this.status = PaymentStatus.COMPLETED; this.transactionId = UUID.randomUUID().toString(); } } // Payment method (could be card, digital wallet, etc.) class PaymentMethod extends Entity { private PaymentType type; private String details; // Could be last 4 digits of card or wallet ID private boolean isDefault; public PaymentMethod(PaymentType type, String details, boolean isDefault) { super(); this.type = type; this.details = details; this.isDefault = isDefault; } // Getters and Setters public PaymentType getType() { return type; } public String getDetails() { return details; } public boolean isDefault() { return isDefault; } public void setDefault(boolean isDefault) { this.isDefault = isDefault; } } // Rating submitted after ride completion class Rating extends Entity { private String rideId; private String fromUserId; private String toUserId; private double ratingValue; // 1-5 scale private String comment; private LocalDateTime ratingTime; public Rating(String rideId, String fromUserId, String toUserId, double ratingValue, String comment) { super(); if (ratingValue < 1.0 || ratingValue > 5.0) { throw new IllegalArgumentException("Rating must be between 1 and 5"); } this.rideId = rideId; this.fromUserId = fromUserId; this.toUserId = toUserId; this.ratingValue = ratingValue; this.comment = comment; this.ratingTime = LocalDateTime.now(); } // Getters public String getRideId() { return rideId; } public String getFromUserId() { return fromUserId; } public String getToUserId() { return toUserId; } public double getRatingValue() { return ratingValue; } public String getComment() { return comment; } public LocalDateTime getRatingTime() { return ratingTime; } } // --------- ENUMS --------- enum UserRole { PASSENGER, DRIVER, BOTH, ADMIN } enum VehicleType { SEDAN, SUV, LUXURY, MINIVAN, ELECTRIC, BIKE } enum VehicleStatus { AVAILABLE, BUSY, MAINTENANCE, OFFLINE } enum RideType { STANDARD, PREMIUM, SHARED, EXPRESS } enum RideRequestStatus { PENDING, ACCEPTED, CANCELLED, EXPIRED } enum RideStatus { ASSIGNED, IN_PROGRESS, COMPLETED, CANCELLED } enum PaymentStatus { PENDING, COMPLETED, FAILED, REFUNDED } enum PaymentType { CREDIT_CARD, DEBIT_CARD, DIGITAL_WALLET, CASH } // --------- INTERFACES & STRATEGY PATTERNS --------- // Price calculation strategy interface PricingStrategy { double calculatePrice(RideRequest request); } // Default pricing implementation based on distance and ride type class StandardPricingStrategy implements PricingStrategy { private static final double BASE_FARE = 5.0; private static final double PER_KM_RATE = 1.5; private static final double PREMIUM_MULTIPLIER = 1.5; private static final double SHARED_DISCOUNT = 0.7; private static final double EXPRESS_MULTIPLIER = 1.25; @Override public double calculatePrice(RideRequest request) { double distance = request.getPickupLocation().distanceTo(request.getDropoffLocation()); double baseFare = BASE_FARE + (distance * PER_KM_RATE); switch (request.getRideType()) { case PREMIUM: return baseFare * PREMIUM_MULTIPLIER; case SHARED: return baseFare * SHARED_DISCOUNT; case EXPRESS: return baseFare * EXPRESS_MULTIPLIER; case STANDARD: default: return baseFare; } } } // Dynamic pricing based on demand class DynamicPricingStrategy implements PricingStrategy { private final PricingStrategy baseStrategy; private double demandMultiplier; public DynamicPricingStrategy(PricingStrategy baseStrategy) { this.baseStrategy = baseStrategy; this.demandMultiplier = 1.0; } public void setDemandMultiplier(double multiplier) { if (multiplier < 1.0) { throw new IllegalArgumentException("Demand multiplier must be at least 1.0"); } this.demandMultiplier = multiplier; } @Override public double calculatePrice(RideRequest request) { double basePrice = baseStrategy.calculatePrice(request); return basePrice * demandMultiplier; } } // Driver matching strategy interface DriverMatchingStrategy { List<String> findMatchingDrivers(RideRequest request, Map<String, User> drivers, Map<String, Vehicle> vehicles); } // Default matching based on proximity and vehicle type class ProximityMatchingStrategy implements DriverMatchingStrategy { private static final double MAX_DISTANCE_KM = 5.0; @Override public List<String> findMatchingDrivers(RideRequest request, Map<String, User> drivers, Map<String, Vehicle> vehicles) { // This is a simplified matching algorithm // In a real system, we would use more sophisticated spatial indexing List<String> matchedDrivers = new ArrayList<>(); Location pickupLocation = request.getPickupLocation(); // Filter available drivers with appropriate vehicles for (User driver : drivers.values()) { if (driver.getRole() != UserRole.DRIVER && driver.getRole() != UserRole.BOTH) { continue; } // Find driver's vehicle Vehicle driverVehicle = null; for (Vehicle vehicle : vehicles.values()) { if (vehicle.getOwnerId().equals(driver.getId()) && vehicle.getStatus() == VehicleStatus.AVAILABLE && vehicle.getCapacity() >= request.getPassengerCount()) { driverVehicle = vehicle; break; } } if (driverVehicle == null) { continue; } // Check if vehicle type matches ride type if (!isVehicleCompatibleWithRideType(driverVehicle.getType(), request.getRideType())) { continue; } // Check distance (in a real app, drivers would update their current location) // This is a placeholder assuming driver locations are stored elsewhere // For this example, we'll randomly decide if a driver is close enough if (Math.random() < 0.3) { // 30% chance driver is within range matchedDrivers.add(driver.getId()); } } return matchedDrivers; } private boolean isVehicleCompatibleWithRideType(VehicleType vehicleType, RideType rideType) { switch (rideType) { case PREMIUM: return vehicleType == VehicleType.LUXURY; case STANDARD: return vehicleType == VehicleType.SEDAN || vehicleType == VehicleType.SUV; case SHARED: return vehicleType == VehicleType.MINIVAN || vehicleType == VehicleType.SUV; case EXPRESS: return true; // Any vehicle type can do express default: return false; } } } // Payment processing strategy interface PaymentProcessingStrategy { boolean processPayment(Payment payment); } // Simulated payment processing class DefaultPaymentProcessor implements PaymentProcessingStrategy { @Override public boolean processPayment(Payment payment) { // In a real system, this would integrate with a payment gateway payment.processPayment(); return payment.getStatus() == PaymentStatus.COMPLETED; } } // --------- SERVICES --------- // User service for managing users class UserService { private final Map<String, User> users; public UserService() { this.users = new ConcurrentHashMap<>(); } public User createUser(String name, String email, String phoneNumber, String password, UserRole role) { // Check if email is already taken if (findUserByEmail(email) != null) { throw new IllegalArgumentException("Email already in use"); } User user = new User(name, email, phoneNumber, password, role); users.put(user.getId(), user); return user; } public User getUserById(String userId) { return users.get(userId); } public User findUserByEmail(String email) { return users.values().stream() .filter(user -> user.getEmail().equals(email)) .findFirst() .orElse(null); } public void updateUser(User user) { if (!users.containsKey(user.getId())) { throw new IllegalArgumentException("User not found"); } users.put(user.getId(), user); } public void addPaymentMethod(String userId, PaymentMethod paymentMethod) { User user = getUserById(userId); if (user == null) { throw new IllegalArgumentException("User not found"); } user.addPaymentMethod(paymentMethod); } public boolean authenticate(String email, String password) { User user = findUserByEmail(email); return user != null && user.validatePassword(password); } public List<User> getDrivers() { return users.values().stream() .filter(user -> user.getRole() == UserRole.DRIVER || user.getRole() == UserRole.BOTH) .collect(Collectors.toList()); } public Map<String, User> getAllDrivers() { return users.entrySet().stream() .filter(entry -> entry.getValue().getRole() == UserRole.DRIVER || entry.getValue().getRole() == UserRole.BOTH) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } } // Vehicle service for managing vehicles class VehicleService { private final Map<String, Vehicle> vehicles; public VehicleService() { this.vehicles = new ConcurrentHashMap<>(); } public Vehicle registerVehicle(String model, String make, String licensePlate, int year, int capacity, VehicleType type, String ownerId) { // Check if license plate already exists if (findVehicleByLicensePlate(licensePlate) != null) { throw new IllegalArgumentException("Vehicle with this license plate already exists"); } Vehicle vehicle = new Vehicle(model, make, licensePlate, year, capacity, type, ownerId); vehicles.put(vehicle.getId(), vehicle); return vehicle; } public Vehicle getVehicleById(String vehicleId) { return vehicles.get(vehicleId); } public Vehicle findVehicleByLicensePlate(String licensePlate) { return vehicles.values().stream() .filter(vehicle -> vehicle.getLicensePlate().equals(licensePlate)) .findFirst() .orElse(null); } public List<Vehicle> getVehiclesByOwnerId(String ownerId) { return vehicles.values().stream() .filter(vehicle -> vehicle.getOwnerId().equals(ownerId)) .collect(Collectors.toList()); } public void updateVehicleStatus(String vehicleId, VehicleStatus status) { Vehicle vehicle = getVehicleById(vehicleId); if (vehicle == null) { throw new IllegalArgumentException("Vehicle not found"); } vehicle.setStatus(status); } public Map<String, Vehicle> getAllVehicles() { return new HashMap<>(vehicles); } } // Ride service for managing ride requests and active rides class RideService { private final Map<String, RideRequest> rideRequests; private final Map<String, Ride> rides; private final UserService userService; private final VehicleService vehicleService; private final DriverMatchingStrategy matchingStrategy; private final PricingStrategy pricingStrategy; public RideService(UserService userService, VehicleService vehicleService) { this.rideRequests = new ConcurrentHashMap<>(); this.rides = new ConcurrentHashMap<>(); this.userService = userService; this.vehicleService = vehicleService; this.matchingStrategy = new ProximityMatchingStrategy(); this.pricingStrategy = new StandardPricingStrategy(); } public RideRequest createRideRequest(String passengerId, Location pickup, Location dropoff, int passengerCount, RideType rideType) { // Validate passenger User passenger = userService.getUserById(passengerId); if (passenger == null) { throw new IllegalArgumentException("Passenger not found"); } if (passenger.getRole() != UserRole.PASSENGER && passenger.getRole() != UserRole.BOTH) { throw new IllegalArgumentException("User is not registered as a passenger"); } RideRequest request = new RideRequest(passengerId, pickup, dropoff, passengerCount, rideType); rideRequests.put(request.getId(), request); // Find matching drivers List<String> matchedDrivers = matchingStrategy.findMatchingDrivers( request, userService.getAllDrivers(), vehicleService.getAllVehicles()); for (String driverId : matchedDrivers) { request.addMatchedDriver(driverId); } return request; } public RideRequest getRideRequestById(String requestId) { return rideRequests.get(requestId); } public Ride acceptRideRequest(String requestId, String driverId, String vehicleId) { RideRequest request = getRideRequestById(requestId); if (request == null) { throw new IllegalArgumentException("Ride request not found"); } if (request.getStatus() != RideRequestStatus.PENDING) { throw new IllegalStateException("Ride request is not pending"); } if (!request.getMatchedDriverIds().contains(driverId)) { throw new IllegalArgumentException("Driver is not matched with this request"); } // Validate driver and vehicle User driver = userService.getUserById(driverId); Vehicle vehicle = vehicleService.getVehicleById(vehicleId); if (driver == null) { throw new IllegalArgumentException("Driver not found"); } if (vehicle == null) { throw new IllegalArgumentException("Vehicle not found"); } if (!vehicle.getOwnerId().equals(driverId)) { throw new IllegalArgumentException("Vehicle does not belong to driver"); } if (vehicle.getStatus() != VehicleStatus.AVAILABLE) { throw new IllegalStateException("Vehicle is not available"); } // Calculate fare double fare = request.getEstimatedFare(pricingStrategy); // Create ride Ride ride = new Ride(requestId, driverId, vehicleId, fare); rides.put(ride.getId(), ride); // Update statuses request.setStatus(RideRequestStatus.ACCEPTED); vehicleService.updateVehicleStatus(vehicleId, VehicleStatus.BUSY); return ride; } public Ride getRideById(String rideId) { return rides.get(rideId); } public void startRide(String rideId) { Ride ride = getRideById(rideId); if (ride == null) { throw new IllegalArgumentException("Ride not found"); } ride.startRide(); // Add pickup location to route RideRequest request = getRideRequestById(ride.getRideRequestId()); ride.addRoutePoint(request.getPickupLocation()); } public void completeRide(String rideId) { Ride ride = getRideById(ride
Design a Ridesharing Service
Last edited time
Apr 3, 2025 05:37 AM
Learning Priority
P1
Status