Back to LLD explorer
OOD / Allocation• Patterns: Strategy, Factory, Observer

Amazon Locker

Medium

Problem Summary

Design an automated Amazon Locker network managing package dimensions, optimal locker compartment assignment, one-time secure pickup codes, and door sensor telemetry.

Functional Scope

  • Physical compartments of varied sizes: Small, Medium, Large, Extra Large.
  • Assign best-fit locker compartment to packages using a pluggable AllocationStrategy.
  • Generate cryptographically secure 6-digit PIN and QR pickup token.
  • Deposit flow by courier: opens assigned door and locks upon closure.
  • Customer pickup flow: validates OTP, opens door, and notifies logistics system.

Entity-Relationship (ER) Schema

LockerHub [1] --> [*] LockerCompartment
LockerHub [1] --> [1] AllocationStrategy
Package [1] --> [0..1] LockerCompartment
PickupTicket [1] --> [1] Package
LockerHub [1] --> [*] LockerObserver

Design Approach

Model compartment dimensions and apply the Best-Fit Strategy pattern to minimize space wastage. Send customer notifications through the Observer pattern upon courier closure confirmation.

Core Classes & Models

LockerHub (Physical locker installation context)LockerCompartment (SlotId, CompartmentSize, CompartmentState: EMPTY, OCCUPIED, JAMMED)Package (Dimensions, tracking number)AllocationStrategy (BestFitStrategy, EnergyClusteringStrategy)PickupTicket (6-digit OTP, expiration timestamp)LockerObserver (Notifies customer and logistics cloud)
Code Blueprint
110 lines
/**
 * Low-Level Design: Amazon Locker Delivery System
 * Design Patterns Applied:
 *  - Strategy Pattern: Locker slot allocation strategy (Best-fit size matching)
 *  - Factory Pattern: Package and locker compartment generation
 *  - Observer Pattern: Real-time customer OTP notification and pickup alerts
 */

import java.util.*;

enum LockerSize { SMALL, MEDIUM, LARGE }

// --- [DESIGN PATTERN: Observer] Notification listener for OTP ---
interface CustomerNotifier {
    void onPackageDelivered(String customerEmail, String lockerId, String otpCode);
}

class EmailCustomerNotifier implements CustomerNotifier {
    @Override
    public void onPackageDelivered(String customerEmail, String lockerId, String otpCode) {
        System.out.println("📧 [Email sent to " + customerEmail + "] Your package is in Locker [" 
                           + lockerId + "]. Secret OTP: " + otpCode);
    }
}

class LockerCompartment {
    private final String id;
    private final LockerSize size;
    private boolean occupied = false;
    private String currentOtp = null;

    public LockerCompartment(String id, LockerSize size) { this.id = id; this.size = size; }
    public String getId() { return id; }
    public LockerSize getSize() { return size; }
    public boolean isOccupied() { return occupied; }
    public void deposit(String otp) { this.occupied = true; this.currentOtp = otp; }
    public boolean verifyAndOpen(String otp) {
        if (occupied && currentOtp != null && currentOtp.equals(otp)) {
            this.occupied = false;
            this.currentOtp = null;
            return true;
        }
        return false;
    }
}

// --- [DESIGN PATTERN: Strategy] Locker allocation strategy ---
interface LockerAllocationStrategy {
    LockerCompartment findLocker(List<LockerCompartment> compartments, LockerSize requiredSize);
}

class BestFitAllocationStrategy implements LockerAllocationStrategy {
    @Override
    public LockerCompartment findLocker(List<LockerCompartment> compartments, LockerSize requiredSize) {
        for (LockerCompartment comp : compartments) {
            if (!comp.isOccupied() && comp.getSize().ordinal() >= requiredSize.ordinal()) {
                return comp;
            }
        }
        return null;
    }
}

class AmazonLockerService {
    private final List<LockerCompartment> compartments = new ArrayList<>();
    private final LockerAllocationStrategy allocationStrategy = new BestFitAllocationStrategy();
    private final List<CustomerNotifier> notifiers = new ArrayList<>();

    public void addCompartment(LockerCompartment comp) { compartments.add(comp); }
    public void addNotifier(CustomerNotifier n) { notifiers.add(n); }

    public synchronized boolean depositPackage(String customerEmail, LockerSize size) {
        LockerCompartment slot = allocationStrategy.findLocker(compartments, size);
        if (slot == null) {
            System.out.println("❌ No available locker compartment for size: " + size);
            return false;
        }

        String otp = String.format("%06d", new Random().nextInt(999999));
        slot.deposit(otp);
        for (CustomerNotifier n : notifiers) n.onPackageDelivered(customerEmail, slot.getId(), otp);
        return true;
    }

    public synchronized boolean pickupPackage(String slotId, String otp) {
        for (LockerCompartment slot : compartments) {
            if (slot.getId().equals(slotId)) {
                if (slot.verifyAndOpen(otp)) {
                    System.out.println("🔓 Locker [" + slotId + "] unlocked! Package retrieved.");
                    return true;
                }
            }
        }
        System.out.println("❌ Invalid OTP for Locker [" + slotId + "]!");
        return false;
    }
}

public class Main {
    public static void main(String[] args) {
        AmazonLockerService lockerHub = new AmazonLockerService();
        lockerHub.addNotifier(new EmailCustomerNotifier());

        lockerHub.addCompartment(new LockerCompartment("LC-01", LockerSize.SMALL));
        lockerHub.addCompartment(new LockerCompartment("LC-02", LockerSize.LARGE));

        System.out.println("--- Amazon Locker Simulation ---");
        lockerHub.depositPackage("john.doe@example.com", LockerSize.MEDIUM);
    }
}