Design patterns used in Ride Sharing Service
Couple of design patterns have been described below
1. Singleton Pattern
Purpose: Ensure only one instance of critical classes (e.g., system-wide components like
RideManager
,DriverManager
, orRiderManager
) exists, which manage shared resources or configurations.Example: A
RideManager
class could be a singleton, which manages all ongoing and completed rides, ensuring a consistent state across the application.public class RideManager { private static volatile RideManager instance; private RideManager() { } public static RideManager getInstance() { if (instance == null) { synchronized (RideManager.class) { if (instance == null) { instance = new RideManager(); } } } return instance; } }
2. Factory Method Pattern
Purpose: Create objects without specifying the exact class of the object that will be created. Useful for creating different types of rides (e.g.,
StandardRide
,PremiumRide
, orCarpoolRide
) based on user preferences.Example: The
RideFactory
class can create different ride types depending on the user's choice or ride conditions.public class RideFactory { public static Ride createRide(String type) { switch (type) { case "Standard": return new StandardRide(); case "Premium": return new PremiumRide(); case "Carpool": return new CarpoolRide(); default: throw new IllegalArgumentException("Unknown ride type"); } } }
3. Observer Pattern
Purpose: Allow components to listen to updates or changes in other components. Useful in notifying a rider and driver about ride status updates (like
Ride Requested
,Driver Assigned
,Ride Started
, etc.).Example:
RideStatusPublisher
can notify both theRider
andDriver
about status updates on the ride.public interface RideStatusListener { void onRideStatusChanged(String status); } public class Rider implements RideStatusListener { public void onRideStatusChanged(String status) { System.out.println("Rider notified: " + status); } } public class Driver implements RideStatusListener { public void onRideStatusChanged(String status) { System.out.println("Driver notified: " + status); } }
4. Strategy Pattern
Purpose: Define different strategies for algorithms or business logic that can be selected at runtime. For example, pricing strategies can vary based on peak hours, traffic conditions, or demand.
Example: A
PricingStrategy
interface can have implementations forStandardPricing
,SurgePricing
, andDiscountPricing
, allowing the system to dynamically choose the pricing logic.public interface PricingStrategy { double calculateFare(double distance, double baseFare); } public class SurgePricing implements PricingStrategy { public double calculateFare(double distance, double baseFare) { return baseFare * distance * 1.5; // Example surge pricing multiplier } }
5. Builder Pattern
Purpose: Construct complex objects step-by-step. The builder pattern can be helpful when creating complex objects like a
Ride
orUserProfile
where different fields might be optional or need validation.Example:
RideBuilder
could help build aRide
object with different optional fields like promo codes, pickup notes, etc.public class RideBuilder { private Rider rider; private Driver driver; private Location startLocation; private Location endLocation; public RideBuilder setRider(Rider rider) { this.rider = rider; return this; } public RideBuilder setDriver(Driver driver) { this.driver = driver; return this; } public RideBuilder setStartLocation(Location startLocation) { this.startLocation = startLocation; return this; } public RideBuilder setEndLocation(Location endLocation) { this.endLocation = endLocation; return this; } public Ride build() { return new Ride(rider, driver, startLocation, endLocation); } }
6. Command Pattern
Purpose: Encapsulate requests as objects to handle them more flexibly, like canceling or redoing a command. In ride-sharing, user actions like requesting, canceling, and accepting rides can be encapsulated in command objects.
Example:
RideRequestCommand
,CancelRideCommand
, andCompleteRideCommand
classes can handle the various actions that can be performed on a ride.public interface Command { void execute(); } public class CancelRideCommand implements Command { private Ride ride; public CancelRideCommand(Ride ride) { this.ride = ride; } public void execute() { ride.cancel(); } }
7. Decorator Pattern
Purpose: Add behavior to objects at runtime. For instance, we can apply additional features to rides, like adding insurance, priority booking, or luxury upgrades.
Example:
RideDecorator
can wrap aRide
object, adding specific functionalities (e.g., insurance or priority booking).public abstract class RideDecorator extends Ride { protected Ride decoratedRide; public RideDecorator(Ride ride) { this.decoratedRide = ride; } public abstract double calculateFare(); } public class InsuranceDecorator extends RideDecorator { public InsuranceDecorator(Ride ride) { super(ride); } public double calculateFare() { return decoratedRide.calculateFare() + 5; // Extra insurance fee } }
8. Proxy Pattern
Purpose: Control access to an object. Useful for securing certain actions or validating requests in the ride-sharing system, such as managing ride requests through a proxy layer to handle authorization and logging.
Example: A
RideRequestProxy
can manage access toRideRequestService
, checking if a user is authorized before allowing them to request rides.public class RideRequestProxy implements RideRequestService { private RideRequestService realRideRequestService; public RideRequestProxy(RideRequestService rideRequestService) { this.realRideRequestService = rideRequestService; } public void requestRide(Rider rider, Location start, Location end) { if (isAuthorized(rider)) { realRideRequestService.requestRide(rider, start, end); } } private boolean isAuthorized(Rider rider) { // Authorization logic return true; } }
https://github.com/abcmishra/RideSharingServiceDesign