Real World Example

Patterns Working Together: Ride Fare Engine (Uber / Ola style)

Real code mixes patterns. Here a Factory picks the fare Strategy for the vehicle type, a Decorator adds surge pricing, a Builder creates the ride request, and Observers react when the ride is booked.

Example

import java.util.*;
import java.util.function.Consumer;

public class Main {
    public static void main(String[] args) {
        List<Consumer<String>> observers = List.of(
                msg -> System.out.println("  [Driver app] " + msg),
                msg -> System.out.println("  [Rider SMS]  " + msg));

        List<Ride> rides = List.of(
                Ride.builder().type("auto").km(6).build(),
                Ride.builder().type("sedan").km(12).surge(1.5).build(),
                Ride.builder().type("bike").km(4).build());

        for (Ride r : rides) {
            FareStrategy fare = FareFactory.forType(r.type());
            if (r.surge() > 1) fare = new SurgeFare(fare, r.surge());
            long amount = Math.round(fare.fare(r.km()));
            System.out.println(r.type() + " " + r.km() + " km -> Rs " + amount);
            observers.forEach(o -> o.accept("Booked " + r.type() + " for Rs " + amount));
        }
    }
}

interface FareStrategy { double fare(double km); }

class FareFactory {
    static FareStrategy forType(String type) {
        return switch (type) {
            case "bike" -> km -> 20 + 6 * km;
            case "auto" -> km -> 30 + 12 * km;
            case "sedan" -> km -> 60 + 18 * km;
            default -> throw new IllegalArgumentException(type);
        };
    }
}

record SurgeFare(FareStrategy base, double multiplier) implements FareStrategy {
    public double fare(double km) { return base.fare(km) * multiplier; }
}

record Ride(String type, double km, double surge) {
    static Builder builder() { return new Builder(); }
    static class Builder {
        private String type; private double km; private double surge = 1.0;
        Builder type(String t) { type = t; return this; }
        Builder km(double k) { km = k; return this; }
        Builder surge(double s) { surge = s; return this; }
        Ride build() { return new Ride(Objects.requireNonNull(type), km, surge); }
    }
}

Output

auto 6.0 km -> Rs 102
  [Driver app] Booked auto for Rs 102
  [Rider SMS]  Booked auto for Rs 102
sedan 12.0 km -> Rs 414
  [Driver app] Booked sedan for Rs 414
  [Rider SMS]  Booked sedan for Rs 414
bike 4.0 km -> Rs 44
  [Driver app] Booked bike for Rs 44
  [Rider SMS]  Booked bike for Rs 44

Pattern Cheat Sheet

PatternProblem it solvesSpring / JDK example
Singletonone shared instancedefault bean scope
Builderreadable construction of complex objectsResponseEntity, Lombok @Builder
Factoryhide which class is createdBeanFactory, List.of
Strategyswap algorithmsComparator, injected List<PaymentGateway>
Observernotify many listeners@EventListener, Kafka
Decoratoradd behaviour by wrappingBufferedReader, AOP proxies
Proxy / Template Method / Adaptercontrol access / fixed skeleton / interface conversion@Transactional proxy, JdbcTemplate, HandlerAdapter