Elevator System

LLD

Understand it step by step (한국어로 이해 → 영어로 말하기)

건물 안의 엘리베이터 여러 대를 제어하는 시스템을 설계합니다. 사용자가 어떤 층에서 위/아래 버튼을 누르면, 시스템이 가장 적절한 엘리베이터를 골라 그 층으로 보내야 합니다. 핵심 요구사항은 (1) 여러 대의 엘리베이터 관리, (2) 층 요청 처리와 이동 방향 결정, (3) 엘리베이터 선택 정책을 쉽게 바꿀 수 있는 구조입니다.
  1. 1

    먼저 요구사항을 정리하고 핵심 개념을 enum으로 잡습니다. 이동 방향(UP/DOWN/IDLE)과 문 상태(OPEN/CLOSED)는 값이 정해져 있으니 클래스가 아니라 enum이 맞습니다.

    ⚖️ Trade-off: 방향이나 문 상태를 String이나 int로 표현할 수도 있습니다. 하지만 그러면 잘못된 값('UPP', 99)이 들어갈 수 있습니다.

    ✅ Fix: 정해진 값 집합은 enum으로 만들어 컴파일 시점에 안전하게 막습니다.

    🔧 도구:enum over primitivestype safety

    enum Direction { UP, DOWN, IDLE }
    enum DoorState { OPEN, CLOSED }
    🗣 영어로 말해

    Fixed value sets like direction and door state should be enums, not strings.

    checking microphone…

  2. 2

    사용자의 호출을 표현하는 Request 클래스를 만듭니다. 어느 층에서 어느 방향으로 가고 싶은지를 담습니다. 한 번 만들면 바뀌지 않으니 필드를 final로 둡니다.

    ✅ Fix: Request는 불변 객체로 두어 여러 엘리베이터 사이에서 안전하게 넘길 수 있게 합니다.

    🔧 도구:immutabilityvalue object

    static final class Request {
        final int floor;
        final Direction direction;
        Request(int floor, Direction direction) {
            this.floor = floor;
            this.direction = direction;
        }
    }
    🗣 영어로 말해

    A Request is an immutable value object holding the source floor and direction.

    checking microphone…

  3. 3

    핵심 엔티티인 Elevator 클래스를 만듭니다. 현재 층, 이동 방향, 문 상태를 가집니다. 가야 할 층들은 위로 가는 정류장과 아래로 가는 정류장으로 나눠 TreeSet에 저장합니다.

    ⚖️ Trade-off: 정류장을 그냥 List에 담을 수도 있지만, 그러면 다음에 어느 층에 멈출지 매번 정렬하거나 찾아야 합니다.

    ✅ Fix: TreeSet을 쓰면 자동 정렬됩니다. 위는 오름차순, 아래는 내림차순(reverseOrder)으로 두면 first()가 항상 다음 목적지입니다.

    🔧 도구:SRPTreeSet for ordered stops

    static final class Elevator {
        final int id;
        int currentFloor = 0;
        Direction direction = Direction.IDLE;
        DoorState door = DoorState.CLOSED;
        final TreeSet<Integer> upStops = new TreeSet<>();
        final TreeSet<Integer> downStops =
            new TreeSet<>(Comparator.reverseOrder());
        Elevator(int id) { this.id = id; }
    }
    🗣 영어로 말해

    Each elevator tracks its floor and keeps up and down stops in sorted sets.

    checking microphone…

  4. 4

    엘리베이터의 핵심 동작을 추가합니다. addStop은 요청 층이 현재 층보다 위면 upStops, 아래면 downStops에 넣고, 현재 층이면 바로 문을 엽니다(놓치면 안 됨). step은 한 틱마다 한 층씩 목적지로 이동합니다.

    ✅ Fix: 방향에 따라 active 집합(upStops 또는 downStops)을 고르고, first()로 다음 목적지를 정해 한 층씩 움직입니다. 도착하면 그 정류장을 지우고 문을 엽니다.

    🔧 도구:SCAN/elevator algorithmbehavior near data

    void addStop(int floor) {
        if (floor > currentFloor) upStops.add(floor);
        else if (floor < currentFloor) downStops.add(floor);
        else door = DoorState.OPEN; // request for the current floor: open now
    }
    void step() {
        if (direction == Direction.IDLE) {
            if (!upStops.isEmpty()) direction = Direction.UP;
            else if (!downStops.isEmpty()) direction = Direction.DOWN;
            else return;
        }
        TreeSet<Integer> active =
            direction == Direction.UP ? upStops : downStops;
        if (active.isEmpty()) { direction = Direction.IDLE; return; }
        int target = active.first();
        currentFloor += Integer.signum(target - currentFloor);
        if (currentFloor == target) { active.remove(target); door = DoorState.OPEN; }
        else door = DoorState.CLOSED;
    }
    🗣 영어로 말해

    Each step moves the car one floor toward the next stop and opens the door on arrival.

    checking microphone…

  5. 5

    어느 엘리베이터를 보낼지 고르는 정책을 인터페이스로 분리합니다. DispatchStrategy를 만들고, 가장 가까운 엘리베이터를 고르는 NearestCarStrategy를 구현합니다.

    ⚖️ Trade-off: 선택 로직을 컨트롤러 안에 if문으로 박을 수도 있습니다. 하지만 나중에 '가장 적게 멈추는 차' 같은 다른 정책을 원하면 컨트롤러를 고쳐야 합니다.

    ✅ Fix: Strategy 패턴으로 정책을 인터페이스 뒤에 두면, 컨트롤러를 안 건드리고 새 정책을 끼워 넣을 수 있습니다 (OCP).

    🔧 도구:Strategy patternOCPDIP

    interface DispatchStrategy {
        Elevator selectElevator(List<Elevator> elevators, Request request);
    }
    static final class NearestCarStrategy implements DispatchStrategy {
        public Elevator selectElevator(List<Elevator> elevators, Request request) {
            Elevator best = null; int bestDist = Integer.MAX_VALUE;
            for (Elevator e : elevators) {
                int d = Math.abs(e.currentFloor - request.floor);
                if (d < bestDist) { bestDist = d; best = e; }
            }
            return best;
        }
    }
    🗣 영어로 말해

    Dispatch logic lives behind a Strategy interface so we can swap selection rules freely.

    checking microphone…

  6. 6

    전체를 묶는 ElevatorController를 만듭니다. 엘리베이터 목록과 정책을 들고 있습니다. requestElevator는 정책으로 차를 고르고 그 차에 정류장을 추가하며, tick은 모든 차를 한 틱 움직입니다.

    ⚖️ Trade-off: 여러 사용자가 동시에 버튼을 누르면 같은 엘리베이터 상태를 동시에 건드려 데이터가 깨질 수 있습니다.

    ✅ Fix: requestElevator를 synchronized로 두어 한 번에 한 요청만 차를 배정하게 합니다. 더 크면 차별로 락을 나누거나 요청 큐를 쓸 수 있습니다.

    🔧 도구:Facade/Controllersynchronizedthread safety

    static final class ElevatorController {
        final List<Elevator> elevators = new ArrayList<>();
        final DispatchStrategy strategy;
        ElevatorController(int count, DispatchStrategy strategy) {
            this.strategy = strategy;
            for (int i = 0; i < count; i++) elevators.add(new Elevator(i));
        }
        synchronized void requestElevator(Request request) {
            Elevator e = strategy.selectElevator(elevators, request);
            if (e != null) e.addStop(request.floor);
        }
        void tick() { for (Elevator e : elevators) e.step(); }
    }
    🗣 영어로 말해

    The controller picks a car via the strategy and synchronizes concurrent requests.

    checking microphone…

  7. 7

    마지막으로 확장 지점을 설명하고 끝을 맺습니다. 현재 구조는 새 정책(예: 부하 분산), 우선순위 요청(소방관 모드), 엘리베이터 상태 머신(고장/점검) 등을 쉽게 붙일 수 있습니다.

    ✅ Fix: Direction/DoorState를 정식 State 패턴으로 키우거나, 도착 알림을 Observer로 표시 패널에 보내는 식으로 확장할 수 있습니다.

    🔧 도구:State pattern (future)Observer (future)extensibility

    // 확장 예: 우선순위 모드, 부하 분산 정책, 상태 머신
    // new ElevatorController(4, new LeastBusyStrategy());
    ElevatorController c = new ElevatorController(2, new NearestCarStrategy());
    c.requestElevator(new Request(5, Direction.UP));
    for (int i = 0; i < 6; i++) c.tick();
    🗣 영어로 말해

    This design extends cleanly to new strategies, priority modes, and state machines.

    checking microphone…

7단계 영어를 다 말하면 → 이 메커니즘 전체를 영어로 설명할 수 있게 된다.

Full Java (javac-verified)

import java.util.*;

public class Solution {

    enum Direction { UP, DOWN, IDLE }
    enum DoorState { OPEN, CLOSED }

    interface DispatchStrategy {
        Elevator selectElevator(List<Elevator> elevators, Request request);
    }

    static final class Request {
        final int floor;
        final Direction direction;
        Request(int floor, Direction direction) {
            this.floor = floor;
            this.direction = direction;
        }
    }

    static final class Elevator {
        final int id;
        int currentFloor = 0;
        Direction direction = Direction.IDLE;
        DoorState door = DoorState.CLOSED;
        final TreeSet<Integer> upStops = new TreeSet<>();
        final TreeSet<Integer> downStops = new TreeSet<>(Comparator.reverseOrder());

        Elevator(int id) { this.id = id; }

        void addStop(int floor) {
            if (floor > currentFloor) upStops.add(floor);
            else if (floor < currentFloor) downStops.add(floor);
        }

        void step() {
            if (direction == Direction.IDLE) {
                if (!upStops.isEmpty()) direction = Direction.UP;
                else if (!downStops.isEmpty()) direction = Direction.DOWN;
                else return;
            }
            TreeSet<Integer> active = direction == Direction.UP ? upStops : downStops;
            if (active.isEmpty()) { direction = Direction.IDLE; return; }
            int target = active.first();
            currentFloor += Integer.signum(target - currentFloor);
            if (currentFloor == target) {
                active.remove(target);
                door = DoorState.OPEN;
            } else {
                door = DoorState.CLOSED;
            }
        }
    }

    static final class NearestCarStrategy implements DispatchStrategy {
        public Elevator selectElevator(List<Elevator> elevators, Request request) {
            Elevator best = null;
            int bestDist = Integer.MAX_VALUE;
            for (Elevator e : elevators) {
                int d = Math.abs(e.currentFloor - request.floor);
                if (d < bestDist) { bestDist = d; best = e; }
            }
            return best;
        }
    }

    static final class ElevatorController {
        final List<Elevator> elevators = new ArrayList<>();
        final DispatchStrategy strategy;

        ElevatorController(int count, DispatchStrategy strategy) {
            this.strategy = strategy;
            for (int i = 0; i < count; i++) elevators.add(new Elevator(i));
        }

        synchronized void requestElevator(Request request) {
            Elevator e = strategy.selectElevator(elevators, request);
            if (e != null) e.addStop(request.floor);
        }

        void tick() { for (Elevator e : elevators) e.step(); }
    }

    public static void main(String[] args) {
        ElevatorController c = new ElevatorController(2, new NearestCarStrategy());
        c.requestElevator(new Request(5, Direction.UP));
        for (int i = 0; i < 6; i++) c.tick();
        System.out.println("Elevator 0 floor: " + c.elevators.get(0).currentFloor);
    }
}
🎙 이 카드 AI랑 음성 대화 · 기록 저장